Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/.cvsignore
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/.cvsignore	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/.cvsignore	(revision 22290)
@@ -0,0 +1,10 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
+*.loT
+*.bb
+*.bbg
+*.da
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/Makefile.am
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/Makefile.am	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/Makefile.am	(revision 22290)
@@ -0,0 +1,24 @@
+#Makefile for fits functions of psLib
+#
+noinst_LTLIBRARIES = libpslibfits.la
+
+libpslibfits_la_CFLAGS = $(SRCINC) $(PSLIB_CFLAGS) $(CFITSIO_CFLAGS) $(AM_CFLAGS)
+libpslibfits_la_SOURCES = \
+	psFits.c \
+	psFitsHeader.c \
+	psFitsImage.c \
+	psFitsTable.c \
+	psFitsFloat.c \
+	psFitsFloatFile.c
+
+EXTRA_DIST = fits.i
+
+pkginclude_HEADERS = \
+	psFits.h \
+	psFitsHeader.h \
+	psFitsImage.h \
+	psFitsTable.h \
+	psFitsFloat.h \
+	psFitsFloatFile.h
+
+CLEANFILES = *~ *.bb *.bbg *.da
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/fits.i
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/fits.i	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/fits.i	(revision 22290)
@@ -0,0 +1,5 @@
+/* fits headers */
+%include "psFits.h"
+%include "psFitsHeader.h"
+%include "psFitsImage.h"
+%include "psFitsTable.h"
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFits.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFits.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFits.c	(revision 22290)
@@ -0,0 +1,879 @@
+/** @file  psFits.c
+ *
+ *  @brief Contains Fits I/O routines
+ *
+ *  @ingroup FileIO
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.76 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-12-25 01:29:42 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <unistd.h>
+#include <string.h>
+
+#include "psFits.h"
+#include "psFitsHeader.h"
+#include "psError.h"
+#include "psAssert.h"
+#include "psImageStructManip.h"
+#include "psMemory.h"
+#include "psString.h"
+#include "psLogMsg.h"
+#include "psTrace.h"
+#include "psVector.h"
+#include "psAbort.h"
+#include "psFitsFloat.h"
+
+#define MAX_STRING_LENGTH 256  // maximum length string for FITS routines
+static char *defaultExtword = "EXTNAME";
+
+static bool isHDUEmpty(const psFits* fits)
+{
+    /* check for keys - no keys means this is really an empty HDU */
+    int keysexist = -1;
+    int morekeys;
+    int status = 0;
+
+    fits_get_hdrspace(fits->fd, &keysexist, &morekeys, &status);
+
+    // if no keys exist and not primary HDU, this really is an empty HDU
+    if (keysexist == 0) {
+        return true;
+    }
+
+    return false;
+
+}
+
+static bool fitsClose(psFits* fits)
+{
+    int status = 0;
+
+    if (fits != NULL) {
+        if (fits_close_file(fits->fd, &status)) {
+            char fitsErr[MAX_STRING_LENGTH];
+            fits_get_errstatus(status, fitsErr);
+            psError(PS_ERR_BAD_FITS, true,
+                    "Error while closing psFits object. CFITSIO error: %s",
+                    fitsErr);
+            return false;
+        }
+        fits->fd = NULL;
+    }
+    return true;
+}
+
+static void fitsFree(psFits* fits)
+{
+    if (!fits) return;
+    if (fits->fd) {
+        fitsClose(fits);
+    }
+    psFree (fits->extword);
+}
+
+bool psFitsClose(psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    bool status = fitsClose(fits);
+    psFree(fits);
+
+    return status;
+}
+
+psFits* psFitsOpen(const char* name, const char* mode)
+{
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    int status = 0;
+    fitsfile *fptr = NULL;      /* Pointer to the FITS file */
+
+    /* check the mode to determine how to open/create file */
+    int iomode;
+    bool newFile;
+    if (strcmp(mode,"r") == 0) {
+        iomode = READONLY;
+        newFile = false;
+    } else if (strcmp(mode,"rw") == 0 || strcmp(mode,"r+") == 0) {
+        iomode = READWRITE;
+        newFile = false;
+    } else if (strcmp(mode,"w") == 0 || strcmp(mode,"w+") == 0) {
+        iomode = READWRITE;
+        newFile = true;
+    } else if (strcmp(mode,"a") == 0|| strcmp(mode,"a+") == 0) {
+        iomode = READWRITE;
+        newFile = (access(name, F_OK) != 0);
+    } else {
+        // mode is not valid
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Specified mode, '%s', is invalid.  Supported modes are r, r+, rw, w, w+, a, or a+.",
+                mode);
+        return NULL;
+    }
+
+    if (newFile) {
+        /* Check if an existing file is in the way before creating file*/
+        if (access(name, F_OK) == 0) {
+            // file exists, delete old one first
+            remove(name);
+        }
+
+        #if ( CFITSIO_DISKFILE == 1 )
+        (void)fits_create_diskfile
+        #else
+        (void)fits_create_file
+        #endif
+        (&fptr, name, &status);
+        if (fptr == NULL || status != 0) {
+            char fitsErr[MAX_STRING_LENGTH];
+            fits_get_errstatus(status, fitsErr);
+            psError(PS_ERR_IO, true,
+                    _("Could not create file,'%s'. CFITSIO Error: %s"),
+                    name, fitsErr);
+            return NULL;
+        }
+    } else {
+        #if ( CFITSIO_DISKFILE == 1 )
+        (void)fits_open_diskfile
+        #else
+        (void)fits_open_file
+        #endif
+        (&fptr, name, iomode, &status);
+        if (fptr == NULL || status != 0) {
+            char fitsErr[MAX_STRING_LENGTH];
+            fits_get_errstatus(status, fitsErr);
+            psError(PS_ERR_IO, true,
+                    _("Could not open file,'%s'. CFITSIO Error: %s"),
+                    name, fitsErr);
+            return NULL;
+        }
+    }
+
+    psFits* fits = psAlloc(sizeof(psFits));
+    fits->fd = fptr;
+    fits->writable = (iomode == READWRITE);
+    fits->extword = NULL;
+    fits->conventions.compression = true;
+    fits->conventions.psBitpix = true;
+    fits->bitpix = 0;
+    fits->floatType = PS_FITS_FLOAT_NONE;
+    psMemSetDeallocator(fits,(psFreeFunc)fitsFree);
+
+    return fits;
+}
+
+psErrorCode p_psFitsError(const char* filename, unsigned int lineno, const char* func, int status,
+                          bool new, const char *errorMsg, ...)
+{
+    if (status == 0) {
+        return PS_ERR_NONE;
+    }
+
+    va_list ap;                         // Variable arguments
+    va_start(ap, errorMsg);
+    psString msg = NULL;                // Message to pass to psError
+    psStringAppendV(&msg, errorMsg, ap);
+    va_end(ap);
+
+    char cfitsioMsg[MAX_STRING_LENGTH];   // Error message from cfitsio
+    (void)fits_get_errstatus(status, cfitsioMsg);
+
+    psStringAppend(&msg, "[CFITSIO error: %s]", cfitsioMsg);
+
+    psErrorCode code = p_psError(filename, lineno, func, PS_ERR_IO, new, msg); // Error code
+    psFree(msg);
+    return code;
+}
+
+static void psFitsCompressionFree(psFitsCompression *comp)
+{
+    PS_ASSERT_PTR_NON_NULL(comp,);
+    psFree(comp->tilesize);
+}
+
+psFitsCompression* psFitsCompressionAlloc(
+    psFitsCompressionType type,         ///< type of compression
+    psVector *tilesize,                 ///< vector defining compression tile size
+    int noisebits,                      ///< noise bits
+    int scale,                          ///< hcompress scale
+    int smooth                          ///< hcompress smothing
+)
+{
+    psFitsCompression *comp = psAlloc(sizeof(psFitsCompression));
+    psMemSetDeallocator(comp, (psFreeFunc) psFitsCompressionFree);
+
+    comp->type = type;
+    comp->tilesize = psVectorCopy(NULL, tilesize, PS_DATA_S64);
+    comp->noisebits = noisebits;
+    comp->scale = scale;
+    comp->smooth = smooth;
+
+    return comp;
+}
+
+bool psMemCheckFits(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc)fitsFree );
+}
+
+bool psFitsSetExtnameWord (psFits *fits, const char *extword)
+{
+    PS_ASSERT_PTR_NON_NULL(fits,    false);
+    PS_ASSERT_PTR_NON_NULL(extword, false);
+
+    psFree (fits->extword);
+    fits->extword = psStringCopy (extword);
+    return true;
+}
+
+// Files compressed with cfitsio's "imcopy" program may have multiple EXTNAME keywords, with the first set to
+// COMPRESSED_IMAGE.  However, fits_movnam_hdu won't find the second (proper) value of EXTNAME, and so can
+// fail to find a perfectly legitimate extension, simply because imcopy does something silly.  However, we
+// really want to be able to read these files (MegaCam data are shipped as imcopy-compressed images).
+// Therefore, we implement our own version of moving to an extension specified by name.  The pure cfitsio
+// version is used if "conventions.compression" handling is turned off in the psFits structure.
+bool psFitsMoveExtName(const psFits* fits,
+                       const char* extname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_STRING_NON_EMPTY(extname, false);
+
+    int status = 0;
+
+    if (!fits->conventions.compression && !fits->extword) {
+        // User wants to use cfitsio.  Good luck to them!
+        if (fits_movnam_hdu(fits->fd, ANY_HDU, (char*)extname, 0, &status) != 0) {
+            psFitsError(status, true, _("Could not find HDU '%s'"), extname);
+            return false;
+        }
+        return true;
+    }
+
+    bool ignoreCI = (fits->conventions.compression &&
+                     (strcmp(extname, "COMPRESSED_IMAGE") != 0)); // Ignore COMPRESSED_IMAGE extension name?
+    char *extword = (fits->extword ? fits->extword : "EXTNAME"); // Word to use as extension name
+
+#if 0
+    // XXX Future optimisation: loop through from the current HDU to the end, then from the start to the
+    // current position.  This will save seeking through the file multiple times.
+    int currentExt = psFitsGetExtNum(fits); // Current extension number
+    int numExt = psFitsGetSize(fits);   // Total number of extensions
+#endif
+
+    for (int i = 1; true; i++) {
+        int hdutype = 0;
+        if (fits_movabs_hdu(fits->fd, i, &hdutype, &status)) {
+            // We've run off the end
+            psFitsError(status, true, _("Could not find HDU with %s = '%s'"), extword, extname);
+            return false;
+        }
+        // Is there a keyword called 'extword'? (read as string regardless of type)
+        char name[MAX_STRING_LENGTH];  // Name of extension
+        if (fits_read_keyword(fits->fd, extword, name, NULL, &status)) {
+            // It doesn't exist in the header.
+            // This isn't the extension you're looking for.  Move along.
+            status = 0;
+            continue;
+        }
+        char *fixed = p_psFitsHeaderParseString(name); // Parsed version (removing quotes and spaces)
+
+        if (ignoreCI && strcmp(fixed, "COMPRESSED_IMAGE") == 0) {
+            // Read it again, Sam
+            if (fits_read_keyword(fits->fd, extword, name, NULL, &status)) {
+                status = 0;
+                continue;
+            }
+            fixed = p_psFitsHeaderParseString(name);
+        }
+
+        if (strcmp(fixed, extname) == 0) {
+            // We've arrived
+            return true;
+        }
+    }
+    psAbort("Should never reach here.");
+}
+
+bool psFitsMoveExtNum(const psFits* fits,
+                      int extnum,
+                      bool relative)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    int status = 0;
+    int hdutype = 0;
+
+    if (relative) {
+        fits_movrel_hdu(fits->fd, extnum, &hdutype, &status);
+        if (status != 0) {
+            char fitsErr[MAX_STRING_LENGTH];
+            fits_get_errstatus(status, fitsErr);
+            psError(PS_ERR_LOCATION_INVALID, true,
+                    _("Could not move %d HDUs from current position. CFITSIO Error: %s"),
+                    extnum, fitsErr);
+            return false;
+        }
+    } else {
+        fits_movabs_hdu(fits->fd, extnum+1, &hdutype, &status);
+        if (status != 0) {
+            char fitsErr[MAX_STRING_LENGTH];
+            fits_get_errstatus(status, fitsErr);
+            psError(PS_ERR_LOCATION_INVALID, true,
+                    _("Could not move to specified HDU #%d. CFITSIO Error: %s"),
+                    extnum, fitsErr);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool psFitsMoveLast(psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    int size = psFitsGetSize(fits);
+    if (size == 0) { // empty file -- no action needed
+        return true;
+    } else {
+        return psFitsMoveExtNum(fits,size-1,false);
+    }
+}
+
+int psFitsGetExtNum(const psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    int hdunum;
+    return fits_get_hdu_num(fits->fd,&hdunum) - 1;
+}
+
+psString psFitsGetExtName(const psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    int status = 0;
+    char name[MAX_STRING_LENGTH];
+
+    char *extword = (fits->extword == NULL) ? defaultExtword : fits->extword;
+
+    if (fits_read_key_str(fits->fd, extword, name, NULL, &status) != 0) {
+        psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                _("Header keyword %s is not found"), extword);
+        return NULL;
+    }
+    return psStringCopy(name);
+}
+
+bool psFitsSetExtName(psFits* fits, const char* name)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+
+    int status = 0;
+
+    char *extword = (fits->extword == NULL) ? defaultExtword : fits->extword;
+
+    if (fits_update_key_str(fits->fd, extword, (char*)name, NULL, &status) != 0) {
+        char fitsErr[MAX_STRING_LENGTH];
+        (void)fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_IO, true,
+                _("Could not write data to file. CFITSIO Error: %s"),
+                fitsErr);
+        return false;
+    }
+
+    return true;
+}
+
+bool psFitsDeleteExtNum(psFits* fits,
+                        int extnum,
+                        bool relative)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+
+    // move to the specified HDU
+    if (! psFitsMoveExtNum(fits,extnum,relative) ) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Failed to delete HDU #%d",
+                extnum);
+        return false;
+    }
+
+    int status = 0;
+
+    // OK, now let's delete the HDU
+    if (fits_delete_hdu(fits->fd, NULL, &status) != 0) {
+        char fitsErr[MAX_STRING_LENGTH];
+        (void)fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_IO, true,
+                _("Could not write data to file. CFITSIO Error: %s"),
+                fitsErr);
+        return false;
+    }
+
+    return true;
+}
+
+bool psFitsDeleteExtName(psFits* fits,
+                         const char* extname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_STRING_NON_EMPTY(extname, false);
+
+    // move to the specified HDU
+    if (! psFitsMoveExtName(fits,extname) ) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Failed to delete HDU with the name '%s'",
+                extname);
+        return false;
+    }
+
+
+    int status = 0;
+
+    // OK, now let's delete the HDU
+    if (fits_delete_hdu(fits->fd, NULL, &status) != 0) {
+        char fitsErr[MAX_STRING_LENGTH];
+        (void)fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_IO, true,
+                _("Could not write data to file. CFITSIO Error: %s"),
+                fitsErr);
+        return false;
+    }
+
+    return true;
+}
+
+int psFitsGetSize(const psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+
+    int num = 0;
+    int status = 0;
+
+    if (fits_get_num_hdus(fits->fd, &num, &status) != 0) {
+        char fitsErr[MAX_STRING_LENGTH];
+        fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_LOCATION_INVALID, true,
+                _("Failed to determine the number of HDUs. CFITSIO Error: %s"),
+                fitsErr);
+        return 0;
+    }
+
+    return num;
+}
+
+psFitsType psFitsGetExtType(const psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, PS_FITS_TYPE_NONE);
+
+    int status = 0;
+    int hdutype = PS_FITS_TYPE_NONE;
+
+    if (fits_get_hdu_type(fits->fd, &hdutype, &status) != 0) {
+        char fitsErr[MAX_STRING_LENGTH];
+        fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_LOCATION_INVALID, true,
+                _("Failed to determine an HDU type. CFITSIO Error: %s"),
+                fitsErr);
+        return PS_FITS_TYPE_NONE;
+    }
+
+    if (hdutype == PS_FITS_TYPE_IMAGE &&
+            psFitsGetExtNum(fits) > 0 &&
+            isHDUEmpty(fits)) {
+        return PS_FITS_TYPE_ANY;
+    }
+
+    return hdutype;
+}
+
+bool psFitsTruncate(psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+
+    int newEnd = psFitsGetExtNum(fits);
+
+    psFitsMoveLast(fits);
+    int end = psFitsGetExtNum(fits);
+
+    // delete HDUs from end to beginning position + 1;
+    for (int lcv=end;lcv > newEnd; lcv--) {
+        if (! psFitsDeleteExtNum(fits,lcv,false)) {
+            // failed to delete an HDU!?
+            psError(PS_ERR_UNKNOWN, false,
+                    "Failed to truncate file.  HDU #%d out of %d could not be deleted.",
+                    lcv,end);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+bool psFitsSetCompression(
+    psFits* fits,                       ///< psFits object to close
+    psFitsCompressionType type,         ///< type of compression
+    psVector *tilesize,
+    int noisebits,                      ///< noise bits
+    int scale,                          ///< hcompress scale
+    int smooth                          ///< hcompress smothing
+)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+
+    // convert psFitsCompressionType to cfitsio compression types
+    int comptype;
+    switch (type) {
+        case PS_FITS_COMPRESS_NONE:
+            comptype = 0x0;
+            break;
+        case PS_FITS_COMPRESS_GZIP:
+            comptype = GZIP_1;
+            break;
+        case PS_FITS_COMPRESS_RICE:
+            comptype = RICE_1;
+            break;
+        case PS_FITS_COMPRESS_HCOMPRESS:
+            comptype = HCOMPRESS_1;
+            break;
+        case PS_FITS_COMPRESS_PLIO:
+            comptype = PLIO_1;
+            break;
+        default:
+            psError(PS_ERR_UNKNOWN, true, "invalid psFitsCompressionType");
+            return false;
+    }
+
+    int status = 0;
+    if (fits_set_compression_type(fits->fd, comptype, &status)) {
+        char fitsErr[MAX_STRING_LENGTH];
+        fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_BAD_FITS, true,
+            "Error while configuring compression. CFITSIO error: %s", fitsErr);
+        return false;
+    }
+
+    // if we are setting a trivial compression (NONE), don't bother with the other parameters
+    if (type == PS_FITS_COMPRESS_NONE) return true;
+
+    // convert a psVector into the (long *) array that cfitsio requires
+    psVector *dim = NULL;
+    if (sizeof(long) == sizeof(psS64)) {
+        dim = psVectorCopy(NULL, tilesize, PS_DATA_S64);
+        fits_set_tile_dim(fits->fd, psVectorLength(dim), (long *)dim->data.S64, &status);
+    } else if (sizeof(long) == sizeof(psS32)) {
+        dim = psVectorCopy(NULL, tilesize, PS_DATA_S32);
+        fits_set_tile_dim(fits->fd, psVectorLength(dim), (long *)dim->data.S32, &status);
+    } else {
+        psAbort("can't map (long) type to a psLib type");
+    }
+    psFree(dim);
+    // status check belongs to fits_set_tile_dim() call
+    if (status) {
+        fits_set_compression_type(fits->fd, 0x0, &status);
+        char fitsErr[MAX_STRING_LENGTH];
+        fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_BAD_FITS, true,
+            "Error while configuring compression. CFITSIO error: %s", fitsErr);
+        return false;
+    }
+
+    // noise bits are irrelevant (not allowed) for PLIO.  XXX actually, it is the data type
+    // that is the restriction; data must be 32 or 64 bit for noise bits to be valid.
+    if (type != PS_FITS_COMPRESS_PLIO) {
+	if (fits_set_noise_bits(fits->fd, noisebits, &status)) {
+	    fits_set_compression_type(fits->fd, 0x0, &status);
+	    char fitsErr[MAX_STRING_LENGTH];
+	    fits_get_errstatus(status, fitsErr);
+	    psError(PS_ERR_BAD_FITS, true,
+		    "Error while configuring compression. CFITSIO error: %s", fitsErr);
+	    return false;
+	}
+    }
+
+#if FITS_HCOMP
+    if (fits_set_hcomp_scale(fits->fd, scale, &status)) {
+        fits_set_compression_type(fits->fd, 0x0, &status);
+        char fitsErr[MAX_STRING_LENGTH];
+        fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_BAD_FITS, true,
+            "Error while configuring compression. CFITSIO error: %s", fitsErr);
+        return false;
+    }
+    if (fits_set_hcomp_smooth(fits->fd, smooth, &status)) {
+        fits_set_compression_type(fits->fd, 0x0, &status);
+        char fitsErr[MAX_STRING_LENGTH];
+        fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_BAD_FITS, true,
+            "Error while configuring compression. CFITSIO error: %s", fitsErr);
+        return false;
+    }
+#endif // FITS_HCOMP
+
+    return true;
+}
+
+psFitsCompression *psFitsCompressionGet(psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    int status = 0;                     // cfitsio status
+
+    psFitsCompressionType type = psFitsCompressionGetType(fits);
+    if (type < 0) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to get compression type.");
+        return NULL;
+    }
+
+    psElemType tileType;                // Type corresponding to "long"
+    if (sizeof(long) == sizeof(psS64)) {
+        tileType = PS_TYPE_S64;
+    } else if (sizeof(long) == sizeof(psS32)) {
+        tileType = PS_TYPE_S32;
+    } else {
+        psAbort("can't map (long) type to a psLib type");
+    }
+
+    psVector *tiles = psVectorAlloc(3, tileType); // Tile sizes
+    if (fits_get_tile_dim(fits->fd, 3, (long*)tiles->data.U8, &status)) {
+        psFitsError(status, true, "Unable to get compression tile sizes.");
+        psFree(tiles);
+        return NULL;
+    }
+
+    int noisebits;                      // Noise bits for compression
+    if (fits_get_noise_bits(fits->fd, &noisebits, &status)) {
+        psFitsError(status, true, "Unable to get compression noise bits.");
+        psFree(tiles);
+        return NULL;
+    }
+
+    int hscale = 0, hsmooth = 0;        // Scaling and smoothing for HCOMPRESS
+
+#if FITS_HCOMP
+    if (fits_get_hcomp_scale(fits->fd, &hscale, &status)) {
+        psFitsError(status, true, "Unable to get HCOMPRESS scaling.");
+        psFree(tiles);
+        return NULL;
+    }
+    if (fits_get_hcomp_smooth(fits->fd, &hsmooth, &status)) {
+        psFitsError(status, true, "Unable to get HCOMPRESS smoothing.");
+        psFree(tiles);
+        return NULL;
+    }
+#endif // FITS_HCOMP
+
+    psFitsCompression *compress = psFitsCompressionAlloc(type, tiles, noisebits, hscale, hsmooth);
+    psFree(tiles);                      // Drop reference
+
+    return compress;
+}
+
+psFitsCompressionType psFitsCompressionGetType(psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, -1);
+
+    int status = 0;                     // cfitsio status
+    int comptype = 0;                   // cfitsio compression type
+    if (fits_get_compression_type(fits->fd, &comptype, &status)) {
+        psFitsError(status, true, "Unable to get compression type.");
+        return -1;
+    }
+
+    psFitsCompressionType type;
+    switch (comptype) {
+      case 0:
+        type = PS_FITS_COMPRESS_NONE;
+        break;
+      case GZIP_1:
+        type = PS_FITS_COMPRESS_GZIP;
+        break;
+      case RICE_1:
+        type = PS_FITS_COMPRESS_RICE;
+        break;
+      case HCOMPRESS_1:
+        type = PS_FITS_COMPRESS_HCOMPRESS;
+        break;
+      case PLIO_1:
+        type = PS_FITS_COMPRESS_PLIO;
+        break;
+      default:
+        psError(PS_ERR_UNKNOWN, true, "cfitsio reports unknown compression type.");
+        return -1;
+    }
+
+    return type;
+}
+
+
+bool psFitsCompressionApply(
+    psFits* fits,                       ///< psFits object to close
+    psFitsCompression *comp             ///< options object
+)
+{
+    return psFitsSetCompression(fits, comp->type, comp->tilesize, comp->noisebits, comp->scale, comp->smooth);
+}
+
+
+psDataType p_psFitsTypeFromCfitsio(int datatype)
+{
+    switch (datatype) {
+    case TBYTE:
+        return PS_TYPE_U8;
+    case TSBYTE:
+        return PS_TYPE_S8;
+    case TSHORT:
+        return PS_TYPE_S16;
+    case TUSHORT:
+        return PS_TYPE_U16;
+    case TLONG:
+        if (sizeof(long) == 8) {
+            return PS_TYPE_S64;
+        }
+        // no break
+    case TINT:
+        return PS_TYPE_S32;
+    case TULONG:
+        if (sizeof(unsigned long) == 8) {
+            return PS_TYPE_U64;
+        }
+        // no break
+    case TUINT:
+        return PS_TYPE_U32;
+    case TLONGLONG:
+        return PS_TYPE_S64;
+    case TFLOAT:
+        return PS_TYPE_F32;
+    case TDOUBLE:
+        return PS_TYPE_F64;
+    case TLOGICAL:
+        return PS_TYPE_BOOL;
+    default:
+        psError(PS_ERR_IO, true,
+                "Unknown FITS datatype, %d.",
+                datatype);
+        return 0;
+    }
+}
+
+bool p_psFitsTypeToCfitsio(psDataType type, int* bitPix, double* bZero, int* dataType)
+{
+    int bitpix;
+    int datatype;
+    double bzero = 0.0;
+
+    switch (type) {
+
+    case PS_TYPE_U8:
+        bitpix = BYTE_IMG;
+        // bzero = -1.0 * INT8_MIN;
+        datatype = TBYTE;
+        break;
+
+    case PS_TYPE_S8:
+        bitpix = BYTE_IMG;
+        bzero = +1.0 * INT8_MIN;
+        datatype = TSBYTE;
+        break;
+
+    case PS_TYPE_U16:
+        bitpix = SHORT_IMG;
+        bzero = -1.0 * INT16_MIN;
+        datatype = TUSHORT;
+        break;
+
+    case PS_TYPE_S16:
+        bitpix = SHORT_IMG;
+        datatype = TSHORT;
+        break;
+
+    case PS_TYPE_U32:
+        bitpix = LONG_IMG;
+        bzero = -1.0 * INT32_MIN;
+        datatype = TUINT;
+        break;
+
+    case PS_TYPE_S32:
+        bitpix = LONG_IMG;
+        datatype = TINT;
+        break;
+
+    case PS_TYPE_F32:
+        bitpix = FLOAT_IMG;
+        datatype = TFLOAT;
+        break;
+
+    case PS_TYPE_F64:
+        bitpix = DOUBLE_IMG;
+        datatype = TDOUBLE;
+        break;
+
+    case PS_DATA_STRING:
+        bitpix = BYTE_IMG;
+        datatype = TSTRING;
+        break;
+
+    case PS_DATA_BOOL:
+        bitpix = BYTE_IMG;
+        datatype = TLOGICAL;
+        break;
+
+    default: {
+            char* typeStr;
+            PS_TYPE_NAME(typeStr,type);
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    _("Specified type, %s, is not supported."),
+                    typeStr);
+            return false;
+        }
+    }
+
+    // pass back the requested parameters  (NULL parameters are not set, of course).
+    if (bitPix != NULL) {
+        *bitPix = bitpix;
+    }
+
+    if (dataType != NULL) {
+        *dataType = datatype;
+    }
+
+    if (bZero != NULL) {
+        *bZero = bzero;
+    }
+
+    return true;
+}
+
+
+psFitsCompressionType psFitsCompressionTypeFromString(const char *string)
+{
+    if (!string || strlen(string) == 0) {
+        psWarning("Unable to identify compression type --- none set.");
+        return PS_FITS_COMPRESS_NONE;
+    }
+
+    if (strcmp(string, "NONE") == 0) return PS_FITS_COMPRESS_NONE;
+    if (strcmp(string, "GZIP") == 0) return PS_FITS_COMPRESS_GZIP;
+    if (strcmp(string, "RICE") == 0) return PS_FITS_COMPRESS_RICE;
+    if (strcmp(string, "HCOMPRESS") == 0) return PS_FITS_COMPRESS_HCOMPRESS;
+    if (strcmp(string, "PLIO") == 0) return PS_FITS_COMPRESS_PLIO;
+
+    psWarning("Unable to identify compression type (%s) --- none set.", string);
+    return PS_FITS_COMPRESS_NONE;
+}
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFits.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFits.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFits.h	(revision 22290)
@@ -0,0 +1,322 @@
+/* @file  psFits.h
+ * @brief Contains Fits I/O routines
+ *
+ * @author Robert DeSonia, MHPCC
+ *
+ * @version $Revision: 1.35 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-16 01:04:56 $
+ * Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PS_FITS_H
+#define PS_FITS_H
+
+/// @addtogroup FileIO Input/Output
+/// @{
+
+#include <fitsio.h>
+#include "psType.h"
+#include "psArray.h"
+#include "psVector.h"
+#include "psMetadata.h"
+#include "psImage.h"
+#include "psFitsFloat.h"
+
+#include "psErrorCodes.h"
+
+/** FITS HDU type.
+ *
+ *  Enumeration for FITS HDU type.
+ *
+ */
+typedef enum {
+    PS_FITS_TYPE_NONE = -1,            ///< Unknown HDU type
+    PS_FITS_TYPE_IMAGE = IMAGE_HDU,    ///< Image HDU type
+    PS_FITS_TYPE_BINARY_TABLE = BINARY_TBL, ///< Binary table HDU type
+    PS_FITS_TYPE_ASCII_TABLE = ASCII_TBL,   ///< ASCII table HDU type
+    PS_FITS_TYPE_ANY = ANY_HDU         ///< Any HDU type
+} psFitsType;
+
+typedef enum {
+    PS_FITS_COMPRESS_NONE = 0,
+    PS_FITS_COMPRESS_GZIP,
+    PS_FITS_COMPRESS_RICE,
+    PS_FITS_COMPRESS_HCOMPRESS,
+    PS_FITS_COMPRESS_PLIO
+} psFitsCompressionType;
+
+/** FITS file object.
+ *
+ *  This object should be considered opaque to the user; no item in this
+ *  struct should be accessed directly.
+ *
+ */
+typedef struct {
+    fitsfile* fd;                       ///< the CFITSIO fits files handle.
+    bool writable;                      ///< Is the file writable?
+    char *extword;                      ///< user-specified word to name extensions (NULL implies EXTNAME)
+    struct {
+        bool compression;               ///< Compression convention: handling of compressed images
+        bool psBitpix;                  ///< Custom floating-point image
+    } conventions;                      ///< Conventions to honour
+    int bitpix;                         ///< Desired BITPIX for output images; 0 to use as provided
+    psFitsFloat floatType;              ///< Desired custom floating-point for output images
+} psFits;
+
+/** FITS compression settings. */
+typedef struct {
+    psFitsCompressionType type;         ///< type of compression
+    psVector *tilesize;                 ///< vector defining compression tile size
+    int noisebits;                      ///< noise bits
+    int scale;                          ///< hcompress scale
+    int smooth;                         ///< hcompress smothing
+} psFitsCompression;
+
+/** Opens a FITS file and allocates the associated psFits object.
+ *
+ *  @return psFits*    new psFits object for the FITS files specified or
+ *                     NULL if the open of the FITS file failed
+ */
+psFits* psFitsOpen(
+    const char* filename,              ///< the FITS file name
+    const char* mode
+    /**< File open mode. Could be one of the following:
+     *       'r' (read only),
+     *       'r+' (read & write),
+     *       'rw' (same as 'r+'), or
+     *       'w' (create new file for writing)
+     */
+);
+
+
+/// Generate an error including the cfitsio error string
+#ifdef DOXYGEN
+psErrorCode psFitsError(
+    int status,                         ///< cfitsio status value
+    bool new,                           ///< new error?
+    const char *errorMsg,               ///< printf-style format of header line
+    ...                                 ///< any parameters required in format
+    );
+#else // ifdef DOXYGEN
+psErrorCode p_psFitsError(
+    const char* filename,               ///< file name
+    unsigned int lineno,                ///< line number in file
+    const char* func,                   ///< function name
+    int status,                         ///< cfitsio status value
+    bool new,                           ///< new error?
+    const char *errorMsg,               ///< printf-style format of header line
+    ...                                 ///< any parameters required in format
+    ) PS_ATTR_FORMAT(printf, 6, 7);
+#ifndef SWIG
+#define psFitsError(status,new,...) \
+      p_psFitsError(__FILE__,__LINE__,__func__,status,new,__VA_ARGS__)
+#endif // ifndef SWIG
+#endif // ifdef DOXYGEN
+
+
+/** Creates a new FITS options struct
+ *
+ *  @return psFitsOptions or NULL on failure
+ */
+psFitsCompression* psFitsCompressionAlloc(
+    psFitsCompressionType type,         ///< type of compression
+    psVector *tilesize,                 ///< vector defining compression tile size
+    int noisebits,                      ///< noise bits
+    int scale,                          ///< hcompress scale
+    int smooth                          ///< hcompress smothing
+);
+
+/// Return the FITS compression type specified by a string
+psFitsCompressionType psFitsCompressionTypeFromString(
+    const char *string                  ///< String with compression type
+    );
+
+/** Closes a FITS file.
+ *
+ *  @return bool      TRUE if FITS file was successfully closed, otherwise FALSE
+ */
+bool psFitsClose(
+    psFits* fits                       ///< psFits object to close
+);
+
+/** Enables/configures FITS compression.
+ *
+ * Note that HCOMPRESS compression is not presently supported.
+ *
+ *  @return bool      TRUE if successfully configured, otherwise FALSE
+ */
+bool psFitsSetCompression(
+    psFits* fits,                       ///< psFits object for which to set compression
+    psFitsCompressionType type,         ///< type of compression
+    psVector *tilesize,                 ///< vector defining compression tile size
+    int noisebits,                      ///< noise bits
+    int scale,                          ///< hcompress scale
+    int smooth                          ///< hcompress smothing
+);
+
+/// Get the compression options for a file handle
+psFitsCompression *psFitsCompressionGet(
+    psFits* fits                        ///< psFits object for which to get compression
+);
+
+/// Get the compression type for a file handle
+psFitsCompressionType psFitsCompressionGetType(
+    psFits* fits                        ///< psFits object for which to get compression type
+    );
+
+/** Sets FITS write options
+ *
+ *  @return bool      TRUE if successfully configured, otherwise FALSE
+ */
+bool psFitsCompressionApply(
+    psFits* fits,                       ///< psFits object for which to set compression
+    psFitsCompression *compress         ///< options object
+);
+
+/** Checks the type of a particular pointer.
+ *
+ *  Uses the appropriate deallocation function in psMemBlock to check the ptr
+ *  datatype.
+ *
+ *  @return bool:       True if the pointer matches a psFits structure, false
+ *                      otherwise.
+ */
+bool psMemCheckFits(
+    psPtr ptr                          ///< the pointer whose type to check
+);
+
+// set the user-defined extension name;
+bool psFitsSetExtnameWord (psFits *fits, const char *extword);
+
+// move to the first HDU where extword == extname.  this is equivalent to fits_movnam_hdu() for
+// a user-defined word in place of EXTNAME
+bool p_psFitsMoveExtName_UserKey(const psFits *fits,
+                                 const char *extname,
+                                 const char *extword);
+
+/** Moves the FITS HDU to the specified extension name.
+ *
+ *  @return bool        TRUE if the extension name was found and move was
+ *                      successful, otherwise FALSE
+ */
+bool psFitsMoveExtName(
+    const psFits* fits,                ///< the psFits object to move
+    const char* extname                ///< the extension name
+);
+
+/** Moves the FITS HDU to the specified extension number
+ *
+ *  @return bool        TRUE if the extension number was found and move was
+ *                      successful, otherwise FALSE
+ */
+bool psFitsMoveExtNum(
+    const psFits* fits,                ///< the psFits object to move
+    int extnum,                        ///< the extension number to move to (zero is primary HDU)
+    bool relative                      ///< if true, extnum is a relative number to the current position
+);
+
+/** Moves the FITS HDU to the end of the file
+ *
+ *  @return bool        TRUE if the move was successful, otherwise FALSE
+*/
+bool psFitsMoveLast(
+    psFits* fits                       ///< the psFits object to move
+);
+
+/** Get the current extension number, where 0 is the primary HDU.
+ *
+ *  @return int        Current HDU number of the psFits file or < 0 if an error
+ *                     occurred.
+ */
+int psFitsGetExtNum(
+    const psFits* fits                 ///< the psFits object
+);
+
+/** Get the current extension name.
+ *
+ *  @return int        Current HDU name of the psFits file or NULL if an
+ *                     error occurred.
+ */
+psString psFitsGetExtName(
+    const psFits* fits                 ///< the psFits object
+);
+
+/** Set the current extension's name
+ *
+ *  @return bool       TRUE if the extension was successfully set, otherwise FALSE.
+ */
+bool psFitsSetExtName(
+    psFits* fits,                      ///< the psFits object
+    const char* name                   ///< the extension name
+);
+
+/** Get the total number of HDUs in the FITS file.
+ *
+ *  @return int        The total number of HDUs in the FITS file or < 0 if an
+ *                     error occurred.
+ */
+int psFitsGetSize(
+    const psFits* fits                 ///< the psFits object
+);
+
+/** Remove the an HDU as specified by number
+ *
+ *  @return bool        TRUE if the specified HDU was removed, otherwise FALSE
+ */
+bool psFitsDeleteExtNum(
+    psFits* fits,                      ///< the psFits object
+    int extnum,                        ///< the extension number to delete (zero is primary HDU)
+    bool relative                      ///< if true, extnum is a relative number to the current position
+);
+
+/** Remove the an HDU as specified by extension name
+ *
+ *  @return bool        TRUE if the specified HDU was removed, otherwise FALSE
+ */
+bool psFitsDeleteExtName(
+    psFits* fits,                      ///< the psFits object
+    const char* extname                ///< the extension name to delete
+);
+
+/** Get the extension type of the current HDU.
+ *
+ *  @return psFitsType The type of the current HDU.  If PS_FITS_TYPE_UNKNOWN,
+ *                     the type could not be determined.
+ */
+psFitsType psFitsGetExtType(
+    const psFits* fits                 ///< the psFits object
+);
+
+/** Delete all extensions after the current position
+ *
+ *  @return bool        TRUE if the operation was successful, otherwise FALSE
+ */
+bool psFitsTruncate(
+    psFits* fits                       ///< the psFits object
+);
+
+// Return the psLib type, given a cfitsio data type
+psDataType p_psFitsTypeFromCfitsio(int datatype // cfitsio data type
+                                  );
+
+// Return the cfitsio data type, given a psLib type
+bool p_psFitsTypeToCfitsio(psDataType type, // psLib data type
+                           int* bitPix, // The corresponding BITPIX (returned)
+                           double* bZero, // The corresponding BZERO (returned)
+                           int* dataType// The corresponding cfitsio data type (returned)
+                          );
+
+#define PS_ASSERT_FITS_NON_NULL(NAME, RVAL) \
+if (!(NAME) || !(NAME)->fd) { \
+    psError(PS_ERR_UNEXPECTED_NULL, true, "Error: FITS file pointer %s is NULL", #NAME); \
+    return RVAL; \
+}
+
+#define PS_ASSERT_FITS_WRITABLE(NAME, RVAL) \
+if (!(NAME)->writable) { \
+    psError(PS_ERR_UNEXPECTED_NULL, true, "Error: FITS file %s not open for writing.", #NAME); \
+    return RVAL; \
+}
+
+/// @}
+#endif // #ifndef PS_FITS_H
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloat.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloat.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloat.c	(revision 22290)
@@ -0,0 +1,155 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ieee754.h>
+#include <string.h>
+#include <assert.h>
+
+#include "psAbort.h"
+#include "psType.h"
+#include "psError.h"
+#include "psImage.h"
+#include "psFits.h"
+#include "psTrace.h"
+#include "psMemory.h"
+
+#include "psFitsFloat.h"
+
+#define BIAS_FLOAT_16_0              10 // Exponent bias for FLOAT_16_0
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Private functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+union float_16_0 {
+    psS16 s16;                      // 16-bit integer version
+
+    // Floating-point version
+    struct {
+        unsigned int negative:1;    // Sign bit
+        unsigned int exponent:5;    // Exponent bits
+        unsigned int mantissa:10;   // Mantissa bits
+    } f16;
+};
+
+
+static inline psS16 convertF32toFloat16_0(psF32 value)
+{
+    union ieee754_float in;             // Input value
+    union float_16_0 out;               // Output value
+
+    // XXX What happens to NAN and INF?
+    in.f = value;
+    out.f16.negative = in.ieee.negative;
+    out.f16.exponent = in.ieee.exponent - IEEE754_FLOAT_BIAS + BIAS_FLOAT_16_0;
+    out.f16.mantissa = in.ieee.mantissa >> 13;
+
+    return out.s16;
+}
+
+static inline psF32 convertF32fromFloat16_0(psS16 value)
+{
+    union float_16_0 in;                // Input value
+    union ieee754_float out;            // Output value
+
+    in.s16 = value;
+    out.ieee.negative = in.f16.negative;
+    out.ieee.exponent = in.f16.exponent + IEEE754_FLOAT_BIAS - BIAS_FLOAT_16_0;
+    out.ieee.mantissa = in.f16.mantissa << 13;
+
+    return out.f;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+psImage *psFitsFloatImageToDisk(const psImage *image, psFitsFloat type)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+
+    psImage *output = NULL;             // Output image, to return
+
+    switch (type) {
+      case PS_FITS_FLOAT_NONE:
+        // No conversion to be performed
+        return psMemIncrRefCounter((psImage*)image); // Casting away "const"
+      case PS_FITS_FLOAT_16_0:
+        output = psImageAlloc(image->numCols, image->numRows, PS_TYPE_S16); // Output image
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                output->data.S16[y][x] = convertF32toFloat16_0(image->data.F32[y][x]);
+            }
+        }
+        break;
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unrecognised custom floating-point type: %x", type);
+        return NULL;
+    }
+
+    return output;
+}
+
+
+psImage *psFitsFloatImageFromDisk(psImage *out, const psImage *in, psFitsFloat type)
+{
+    PS_ASSERT_IMAGE_NON_NULL(in, NULL);
+
+    psElemType elem = psFitsFloatImageType(type); // Type for elements
+    if (elem == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to recognise convention: %x", type);
+        return NULL;
+    }
+
+    int numCols = in->numCols, numRows = in->numRows; // Size of image
+
+    out = psImageRecycle(out, numCols, numRows, elem);
+
+    switch (type) {
+      case PS_FITS_FLOAT_16_0:
+        if (in->type.type != PS_TYPE_S16) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    "Convention claims to be FLOAT_16_0, but image type is not S16.");
+            return NULL;
+        }
+        assert(out->type.type == PS_TYPE_F32);
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                out->data.F32[y][x] = convertF32fromFloat16_0(in->data.S16[y][x]);
+            }
+        }
+        return out;
+      case PS_FITS_FLOAT_NONE:
+      default:
+        psAbort("Should be unreachable");
+    }
+    return NULL;
+}
+
+psElemType psFitsFloatImageType(psFitsFloat type)
+{
+    switch (type) {
+      case PS_FITS_FLOAT_16_0:
+        return PS_TYPE_F32;
+      case PS_FITS_FLOAT_NONE:
+      default:
+        return 0;                       // Doesn't correspond to ANY type --- should flag a real error
+    }
+}
+
+
+psFitsFloat psFitsFloatTypeFromString(const char *string)
+{
+    PS_ASSERT_STRING_NON_EMPTY(string, PS_FITS_FLOAT_NONE);
+
+    if (strcmp(string, "FLOAT_16_0") == 0) {
+        return PS_FITS_FLOAT_16_0;
+    }
+
+    psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+            "Unable to recognise custom floating-point convention name: %s", string);
+    return PS_FITS_FLOAT_NONE;
+}
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloat.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloat.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloat.h	(revision 22290)
@@ -0,0 +1,33 @@
+#ifndef PS_FITS_FLOAT_H
+#define PS_FITS_FLOAT_H
+
+#include <psFits.h>
+#include <psType.h>
+#include <psImage.h>
+
+/// Type of custom floating point
+typedef enum {
+    PS_FITS_FLOAT_NONE,                 ///< No conversion to be performed
+    PS_FITS_FLOAT_16_0,                 ///< Original F16 proposal: 1S 5E 10M
+} psFitsFloat;
+
+/// Convert an image to custom floating-point (the disk representation) in preparation for writing as FITS
+psImage *psFitsFloatImageToDisk(const psImage *image, ///< Image to convert
+                               psFitsFloat type ///< Custom floating point type
+    );
+
+/// Convert the custom floating-point image (the disk representation) to a normal floating-point image
+psImage *psFitsFloatImageFromDisk(psImage *out, ///< Output image, or NULL
+                                  const psImage *in, ///< Image to convert
+                                  psFitsFloat type ///< Custom floating point type
+    );
+
+/// Return the appropriate element type for a custom floating-point
+psElemType psFitsFloatImageType(psFitsFloat type ///< Custom floating-point type
+    );
+
+/// Return the custom floating-point type from a string description
+psFitsFloat psFitsFloatTypeFromString(const char *string ///< String with name of type
+    );
+
+#endif
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloatFile.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloatFile.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloatFile.c	(revision 22290)
@@ -0,0 +1,62 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "psError.h"
+#include "psFits.h"
+#include "psFitsFloat.h"
+#include "psMemory.h"
+
+#include "psFitsFloatFile.h"
+
+bool psFitsFloatImageSet(const psFits *fits, psFitsFloat type)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+
+    char *convName;                     // Convention name
+
+    switch (type) {
+      case PS_FITS_FLOAT_NONE:
+        return true;
+      case PS_FITS_FLOAT_16_0:
+        convName = "FLOAT_16_0";
+        break;
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to recognise convention: %x", type);
+        return false;
+    }
+
+    int status = 0;                     // Status from cfitsio
+    fits_write_key_str(fits->fd, "PSBITPIX", convName, "Custom floating point convention name", &status);
+    if (psFitsError(status, true, "Could not write PSBITPIX header to file.")) {
+        return false;
+    }
+    return true;
+}
+
+
+psFitsFloat psFitsFloatImageCheck(const psFits *fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, PS_FITS_FLOAT_NONE);
+
+    if (!fits->conventions.psBitpix) {
+        return PS_FITS_FLOAT_NONE;
+    }
+
+    int status = 0;                     // Status of CFITSIO calls
+    char convName[FLEN_CARD];           // Convention name for custom floating-point
+    if (fits_read_key_str(fits->fd, "PSBITPIX", convName, NULL, &status) && status != KEY_NO_EXIST) {
+        psFitsError(status, true, "Unable to read header.");
+        return PS_FITS_FLOAT_NONE;
+    }
+
+    if (!convName || status == KEY_NO_EXIST) {
+        return PS_FITS_FLOAT_NONE;
+    }
+
+    return psFitsFloatTypeFromString(convName);
+}
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloatFile.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloatFile.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsFloatFile.h	(revision 22290)
@@ -0,0 +1,20 @@
+// This file exists to resolve the co-dependency problem of placing these function definitions in
+// psFitsFloat.h --- because they refer to psFits, and psFits refers to psFitsFloat, we would end up with a
+// co-dependency problem if these function declarations were to be placed in psFitsFloat.h.
+#ifndef PS_FITS_FLOAT_FILE_H
+#define PS_FITS_FLOAT_FILE_H
+
+#include <psFits.h>
+#include <psFitsFloat.h>
+
+/// Set a flag in the FITS header of the current extension that the image is a custom floating-point
+bool psFitsFloatImageSet(const psFits *fits,  ///< FITS file
+                         psFitsFloat type ///< Custom floating-point type
+    );
+
+/// Check if the current extension contains a custom floating-point, returning the appropriate type
+psFitsFloat psFitsFloatImageCheck(const psFits *fits ///< FITS file
+    );
+
+
+#endif
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsHeader.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsHeader.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsHeader.c	(revision 22290)
@@ -0,0 +1,675 @@
+/** @file  psFitsHeader.c
+ *
+ *  @brief Contains Fits header I/O routines
+ *
+ *  @ingroup FileIO
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-16 01:04:56 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <unistd.h>
+
+#include "psAssert.h"
+#include "psFits.h"
+#include "string.h"
+#include "strings.h"
+#include "psError.h"
+
+#include "psImageStructManip.h"
+#include "psMemory.h"
+#include "psString.h"
+#include "psLogMsg.h"
+#include "psTrace.h"
+#include "psVector.h"
+
+#define MAX_STRING_LENGTH 256  // maximum length string for FITS routines
+#define NUM_EMPTY_KEYS 8                // Number of keywords before header is considered practically empty
+
+// list of FITS header keys to ignore; NULL-terminated
+static const char* ignoreFitsKeys[] = { "", NULL};
+
+// List of FITS header keys that may be duplicated; NULL-terminated
+static const char *duplicateFitsKeys[] = { "COMMENT", "HIERARCH", "HISTORY", NULL};
+
+// List of FITS header keys not to write (handled by cfitsio); NULL-terminated
+static const char *noWriteFitsKeys[] = { "SIMPLE", "XTENSION", "BITPIX", "NAXIS", "EXTNAME", "BSCALE",
+                                         "BZERO", "TFIELDS", "PCOUNT", "GCOUNT", "ZIMAGE", "ZBITPIX",
+                                         "ZCMPTYPE", "PSBITPIX", NULL};
+
+// List of the start of FITS header keys not to write (handled by cfitsio); NULL-terminated
+static const char *noWriteFitsKeyStarts[] = { "NAXIS", "TTYPE", "TFORM", "ZNAXIS", "ZTILE", "ZNAME", "ZVAL",
+                                              NULL};
+
+// List of FITS header keys that may be present if the header is considered "empty"; NULL-terminated
+static const char *emptyKeys[] = { "SIMPLE", "BITPIX", "NAXIS", "EXTEND", "COMMENT", "CHECKSUM", "DATASUM",
+                                   NULL };
+
+// How to translate between keywords
+typedef struct {
+    const char *from;                   // Translate from this keyword
+    const char *to;                     // Translate to this keyword
+} keywordTranslation;
+
+// Translation for compressed image headers           FROM        TO
+static keywordTranslation compressTranslation[] = { { "ZSIMPLE",  "SIMPLE" },
+                                                    { "ZTENSION", "XTENSION" },
+                                                    { "ZBITPIX",  "BITPIX" },
+                                                    { "ZNAXIS",   "NAXIS"  },
+                                                    { "ZNAXIS1",  "NAXIS1" },
+                                                    { "ZNAXIS2",  "NAXIS2" },
+                                                    { "ZNAXIS3",  "NAXIS3" },
+                                                    { "ZEXTEND",  "EXTEND" },
+                                                    { "ZBLOCKED", "BLOCKED"},
+                                                    { "ZPCOUNT",  "PCOUNT" },
+                                                    { "ZGCOUNT",  "GCOUNT" },
+                                                    { "ZHECKSUM", "CHECKSUM"},
+                                                    { "ZDATASUM", "DATASUM" },
+                                                    { NULL,       NULL } };
+
+
+// Compare a keyword with a list of keywords; return true if it's in the list
+static bool keywordInList(const char *keyword, // Keyword to check
+                          const char *list[] // List of keywords
+    )
+{
+    for (const char **check = list; *check; ++check) {
+        if (strcmp(keyword, *check) == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static const char *keywordTranslate(const char *keyword, // Keyword to check
+                                    keywordTranslation translation[] // Translation list
+                                    )
+{
+    for (keywordTranslation *trans = translation; (*trans).from; ++trans) {
+        if (strcmp(keyword, (*trans).from) == 0) {
+            // Translate it
+            return (*trans).to;
+        } else if (strcmp(keyword, (*trans).to) == 0) {
+            // Ignore it completely --- something else will translate to it
+            return NULL;
+        }
+    }
+    // It translates to itself
+    return keyword;
+}
+
+
+bool psFitsCheckSingleCompressedImagePHU(const psFits *fits, psMetadata *header)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!fits->conventions.compression) {
+        // User has turned off compression conventions; doesn't want any nasty surprises
+        return false;
+    }
+
+    if (psFitsGetExtNum(fits) != 0) {
+        // It's not the PHU, so it can't be the PHU for a single compressed image!
+        return false;
+    }
+
+    if (psFitsGetSize(fits) != 2) {
+        // No second extension, or multiple extensions
+        return false;
+    }
+
+    int numKeys;                        // Number of keywords in the header
+    int status = 0;                     // CFITSIO status
+    fits_get_hdrspace(fits->fd, &numKeys, 0, &status);
+    if (numKeys > NUM_EMPTY_KEYS) {
+        return false;
+    }
+
+    int bitpix, naxis;                  // Bits per pixel and number of axes
+    long naxes[MAX_COMPRESS_DIM];       // Dimensions
+    fits_get_img_param(fits->fd, MAX_COMPRESS_DIM, &bitpix, &naxis, naxes, &status);
+    if (naxis != 0) {
+        return false;
+    }
+
+    if (!header) {
+        for (int i = 1; i <= numKeys; i++) {
+            // Just want to read the keyword names, without parsing the values and stuffing into a metadata
+            char keyName[MAX_STRING_LENGTH];// Keyword name
+            char keyValue[MAX_STRING_LENGTH]; // Corresponding value
+            char keyComment[MAX_STRING_LENGTH]; // Corresponding comment
+            fits_read_keyn(fits->fd, i, keyName, keyValue, keyComment, &status);
+            if (!keywordInList(keyName, emptyKeys)) {
+                return false;
+            }
+        }
+    } else {
+        psMetadataIterator *iter = psMetadataIteratorAlloc(header, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;           // Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!keywordInList(item->name, emptyKeys)) {
+                psFree(iter);
+                return false;
+            }
+        }
+        psFree(iter);
+    }
+
+    if (!psFitsMoveExtNum(fits, 1, false)) {
+        psWarning("Unable to examine first extension as suspect compressed image.");
+        return false;
+    }
+
+    if (fits_is_compressed_image(fits->fd, &status)) {
+        return true;
+    }
+
+    // It's not a single compressed image PHU --- move back to the PHU for the user
+    if (!psFitsMoveExtNum(fits, 0, false)) {
+        psWarning("Unable to examine first extension as suspect compressed image.");
+        return false;
+    }
+
+    return false;
+}
+
+char *p_psFitsHeaderParseString(char *string)
+{
+    if (!string || strlen(string) == 0) {
+        return string;
+    }
+
+    char *fixed = string;       // Fixed version of the string
+    // remove the single-quotes at front/end
+    if (fixed[0] == '\'' && fixed[strlen(string)-1] == '\'') {
+        string[strlen(string)-1] = '\0'; // Remove the trailing quote
+        fixed += 1; // Advance past the leading quote
+    }
+    // Remove trailing spaces, which are not significant, according to the FITS standard
+    // http://archive.stsci.edu/fits/fits_standard/node31.html
+    char *lastSpace = NULL; // The last space in the string
+    while (strlen(fixed) > 1 && (lastSpace = strrchr(fixed, ' ')) && lastSpace[1] == '\0') {
+        // This is a trailing space, not a leading space.
+        lastSpace[0] = '\0'; // Truncate the string here
+    }
+
+    return fixed;
+}
+
+// Read the header
+static psMetadata *readHeader(const psFits *fits // FITS file from which to read header
+                              )
+{
+    assert(fits);
+
+    psMetadata *header = psMetadataAlloc(); // Header, to return
+
+    // Get number of key names
+    int numKeys = 0;                    // Number of keywords
+    int keyNum = 0;                     // Current key number
+    int status = 0;                     // Status of cfitsio calls
+    fits_get_hdrpos(fits->fd, &numKeys, &keyNum, &status);
+
+    bool compressed = false;            // Is this a compressed image?
+    if (fits->conventions.compression && fits_is_compressed_image(fits->fd, &status)) {
+        compressed = true;
+    }
+
+    // Get each key name. Keywords start at one.
+    for (int i = 1; i <= numKeys; i++) {
+        char keyName[MAX_STRING_LENGTH];// Keyword name
+        char keyValue[MAX_STRING_LENGTH]; // Corresponding value
+        char keyComment[MAX_STRING_LENGTH]; // Corresponding comment
+        fits_read_keyn(fits->fd, i, keyName, keyValue, keyComment, &status);
+
+        // Check to see if the keyword should be ignored
+        if (keywordInList(keyName, ignoreFitsKeys)) {
+            // We're done here; skip to the next key
+            continue;
+        }
+
+        const char *keyNameTrans = keyName;   // Translated name of keyword
+        if (compressed) {
+            keyNameTrans = keywordTranslate(keyName, compressTranslation);
+            if (!keyNameTrans) {
+                // It's to be ignored (it will be replaced by something else)
+                continue;
+            }
+        }
+
+        // Check to see if the keyword should be duplicated
+        int dupFlag = 0;                // Duplicate flag
+        if (keywordInList(keyName, duplicateFitsKeys)) {
+            dupFlag = PS_META_DUPLICATE_OK;
+        }
+
+        char keyType;                   // Type of key; from cfitsio
+        if (keyValue[0] != 0) { // blank values are not handled by fits_get_keytype
+            fits_get_keytype(keyValue, &keyType, &status);
+        } else {
+            keyType = 'C';
+        }
+        if (status != 0) {
+            break;
+        }
+
+        bool success;                 // Was the add to the metadata successful?
+        switch (keyType) {
+        case 'X': // bit
+        case 'B': // byte
+            success = psMetadataAddS8(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                      atoi(keyValue));
+            break;
+        case 'I': // short int.
+            // This is the default type that cfitsio reports whenever it doesn't know what it is.
+            // Trap NAN, INF and -INF, which cfitsio doesn't handle.
+            if (strncasecmp(keyValue, "NAN", 3) == 0) {
+                success = psMetadataAddF32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment, NAN);
+            } else if (strncasecmp(keyValue, "INF", 3) == 0) {
+                success = psMetadataAddF32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                           INFINITY);
+            } else if (strncasecmp(keyValue, "-INF", 4) == 0) {
+                success = psMetadataAddF32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                           -INFINITY);
+            } else {
+                success = psMetadataAddS32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                           atoi(keyValue));
+            }
+            break;
+        case 'J': // int.
+            success = psMetadataAddS32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                       atoi(keyValue));
+            break;
+        case 'U': // unsigned int.
+            success = psMetadataAddU32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                       atol(keyValue));
+            break;
+
+        case 'K': // long int. can't all fit in a psS32, put in psF64
+        case 'F':
+            success = psMetadataAddF64(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                       atof(keyValue));
+            break;
+        case 'C': {
+            char *keyValueFixed = p_psFitsHeaderParseString(keyValue); // Fixed version of the string
+
+            // Need to trap NAN, INF and -INF written by psFitsWriteHeader.
+            // cfitsio won't write these, so we write them as strings, and then have to trap them on read.
+            if (strcasecmp(keyValueFixed, "NAN") == 0) {
+                success = psMetadataAddF32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment, NAN);
+            } else if (strcasecmp(keyValueFixed, "INF") == 0) {
+                success = psMetadataAddF32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                           INFINITY);
+            } else if (strcasecmp(keyValueFixed, "-INF") == 0) {
+                success = psMetadataAddF32(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                           -INFINITY);
+            } else if (compressed && strcmp(keyName, "EXTNAME") == 0 &&
+                       strcmp(keyValueFixed, "COMPRESSED_IMAGE") == 0) {
+                // Ignore EXTNAME=COMPRESSED_IMAGE if compression convention is to be respected
+                success = true;
+            } else {
+                success = psMetadataAddStr(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment,
+                                           keyValueFixed);
+            }
+            break;
+        }
+        case 'L': {
+            bool temp = (keyValue[0] == 'T') ? 1 : 0;
+            success = psMetadataAddBool(header, PS_LIST_TAIL, keyNameTrans, dupFlag, keyComment, temp);
+            break;
+        }
+        default:
+            psError(PS_ERR_IO, true, _("Specified FITS metadata type, %c, is not supported."), keyType);
+            psFree(header);
+            return NULL;
+        }
+
+        if (!success) {
+            psError(PS_ERR_UNKNOWN, false, _("Failed to add metadata item, %s --> %s."),
+                    keyName, keyNameTrans);
+            psFree(header);
+            return NULL;
+        }
+
+    }
+
+    if (status != 0) {
+        psFitsError(status, true, _("Failed to add metadata item."));
+        psFree(header);
+        return false;
+    }
+
+    return header;
+}
+
+
+psMetadata* psFitsReadHeader(psMetadata* out,
+                             const psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    psMetadata *header = readHeader(fits); // Header
+    if (!header) {
+        return NULL;
+    }
+
+    // Explore the potential case that this is an empty PHU, and the first extension contains the sole image,
+    // which is compressed.
+    if (psFitsCheckSingleCompressedImagePHU(fits, header)) {
+        // This is really what we want, not the empty PHU
+        psTrace("psLib.fits", 1,
+                "This PHU should really be a compressed image --- getting that header instead.");
+        psFree(header);
+        header = readHeader(fits);
+        if (!header) {
+            return NULL;
+        }
+    }
+
+    if (!out) {
+        return header;
+    }
+
+    // Need to move header onto the nominated metadata
+    psMetadataIterator *iter = psMetadataIteratorAlloc(header, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;           // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        // Need to look for MULTI, which won't be picked up using the iterator.
+        psMetadataItem *multiCheckItem = psMetadataLookup(header, item->name);
+        assert(multiCheckItem);
+        unsigned int flag = 0;      // Flag to indicate MULTI; otherwise default action
+        if (multiCheckItem->type == PS_DATA_METADATA_MULTI) {
+            flag = PS_META_DUPLICATE_OK;
+        }
+        if (!psMetadataAddItem(out, item, PS_LIST_TAIL, flag)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to add header item %s to extant metadata.",
+                    item->name);
+            psFree(iter);
+            psFree(header);
+            return NULL;
+        }
+    }
+    psFree(iter);
+    psFree(header);
+    return out;
+}
+
+psMetadata* psFitsReadHeaderSet(psMetadata* out, const psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    if (!out) {
+        out = psMetadataAlloc();
+    }
+
+    int size = psFitsGetSize(fits);
+
+    int origPosition = psFitsGetExtNum(fits);
+
+    for (int lcv=0; lcv < size; lcv++) {
+        psFitsMoveExtNum(fits, lcv, false);
+
+        char* name = NULL;
+        if (lcv == 0) {
+            name = psStringCopy("PHU");
+        } else {
+            name = psFitsGetExtName(fits);
+        }
+
+        psMetadata* header = psFitsReadHeader(NULL, fits);
+        if (name != NULL && header != NULL) {
+            psMetadataAddMetadata(out, PS_LIST_TAIL, name, 0, "FITS Header", header);
+        } else {
+            psWarning("Failed to read HDU#%d header data.",
+                      lcv);
+        }
+
+        psFree(name);
+        psFree(header);
+    }
+
+    // reposition to the original position
+    psFitsMoveExtNum(fits, origPosition, false);
+
+    return out;
+}
+
+static bool fitsWriteHeader(psFits *fits, // The FITS file handle
+                            const psMetadata *output, // Metadata that is to be output into the FITS file
+                            bool keyStarts // Write out the key starts?
+                           )
+{
+    int status = 0;                     // Status of cfitsio calls
+    bool simple = true;                 // If SIMPLE is T, then the file should conform to the FITS standard
+    psMetadataItem *simpleItem = psMetadataLookup(output, "SIMPLE"); // SIMPLE in the header
+    if (simpleItem) {
+        // We allow the user to write SIMPLE, but it must be boolean
+        if (simpleItem->type != PS_DATA_BOOL) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true, "SIMPLE in a FITS header must be of boolean type: "
+                    "not %x --- assuming TRUE.\n", simpleItem->type);
+            int value = false;          // Temporary holder for boolean
+            fits_update_key(fits->fd, TLOGICAL, "SIMPLE", &value,
+                            "File does not conform to FITS standard", &status);
+            simple = false;
+        } else if (!simpleItem->data.B) {
+            simple = false;
+            int value = false;          // Temporary holder for boolean
+            fits_update_key(fits->fd, TLOGICAL, "SIMPLE", &value,
+                            "File does not conform to FITS standard", &status);
+        }
+        // SIMPLE = T is taken care of by cfitsio.
+    }
+
+    // Traverse the metadata list and add each key.
+    psListIterator* iter = psListIteratorAlloc(output->list, PS_LIST_HEAD, true); // Iterator
+    psMetadataItem* item;               // Item from iteration
+    while ((item = psListGetAndIncrement(iter))) {
+        // Check to see if the item should be ignored
+        if (simple) {
+            // We ignore particular (required) keywords, because these are written by CFITSIO
+            // Furthermore, users tend to supply FITS headers that are wrong (e.g., after binning down the
+            // image, the NAXISn haven't been changed; or after converting to F32, the BITPIX hasn't been
+            // changed) so we'll take care of that for them.
+            if (keywordInList(item->name, noWriteFitsKeys)) {
+                // Don't write it; skip to the next key
+                continue;
+            }
+            if (keyStarts) {
+                // Also block out TTYPEn, NAXISn, etc --- keywords that start with a certain sequence.
+                // We want to do this when writing an image or table, since it guarantees that the NAXISn etc
+                // that go in are correct.  However, when we're writing a "blank" HDU (header only), we want
+                // to preserve NAXISn etc for reference, so we don't do this.
+                bool writeKey = true;   // Write this keyword?
+                for (int i = 0; noWriteFitsKeyStarts[i] && writeKey; i++) {
+                    if (strncmp(item->name, noWriteFitsKeyStarts[i], strlen(noWriteFitsKeyStarts[i])) == 0) {
+                        writeKey = false;
+                    }
+                }
+                if (!writeKey) {
+                    // Don't write it; skip to the next key
+                    continue;
+                }
+            }
+        }
+
+        if (strcmp(item->name, "COMMENT") == 0) {
+            fits_write_comment(fits->fd, item->comment, &status);
+        } else if (strcmp(item->name,  "HISTORY") == 0) {
+            fits_write_history(fits->fd, item->comment, &status);
+        } else {
+            // A regular FITS header
+            switch (item->type) {
+            case PS_DATA_BOOL: {
+                    int value = item->data.B;
+                    fits_update_key(fits->fd, TLOGICAL, item->name, &value, item->comment, &status);
+                    break;
+                }
+            case PS_DATA_S8:
+                fits_update_key(fits->fd, TBYTE, item->name, &item->data.S8, item->comment, &status);
+                break;
+            case PS_DATA_S16:
+                fits_update_key(fits->fd, TSHORT, item->name, &item->data.S16, item->comment, &status);
+                break;
+            case PS_DATA_S32:
+                fits_update_key(fits->fd, TINT, item->name, &item->data.S32, item->comment, &status);
+                break;
+            case PS_DATA_U8: {
+                    unsigned short int temp = item->data.U8;
+                    fits_update_key(fits->fd, TUSHORT, item->name, &temp, item->comment, &status);
+                }
+                break;
+            case PS_DATA_U16:
+                fits_update_key(fits->fd, TUSHORT, item->name, &item->data.U16, item->comment, &status);
+                break;
+            case PS_DATA_U32:
+                fits_update_key(fits->fd, TUINT, item->name, &item->data.U32, item->comment, &status);
+                break;
+            case PS_DATA_F32: {
+                    int infCheck = 0;         // Result of isinf()
+                    if (isnan(item->data.F32)) {
+                        fits_update_key(fits->fd, TSTRING, item->name, "NaN", item->comment, &status);
+                    } else if ((infCheck = isinf(item->data.F32)) != 0) {
+                        if (infCheck == 1) {
+                            fits_update_key(fits->fd, TSTRING, item->name, "Inf", item->comment, &status);
+                        } else {
+                            fits_update_key(fits->fd, TSTRING, item->name, "-Inf", item->comment, &status);
+                        }
+                    } else {
+                        fits_update_key(fits->fd, TFLOAT, item->name, &item->data.F32, item->comment,
+                                        &status);
+                    }
+                    break;
+                }
+            case PS_DATA_F64: {
+                    int infCheck = 0;         // Result of isinf()
+                    if (isnan(item->data.F64)) {
+                        fits_update_key(fits->fd, TSTRING, item->name, "NaN", item->comment, &status);
+                    } else if ((infCheck = isinf(item->data.F64)) != 0) {
+                        if (infCheck == 1) {
+                            fits_update_key(fits->fd, TSTRING, item->name, "Inf", item->comment, &status);
+                        } else {
+                            fits_update_key(fits->fd, TSTRING, item->name, "-Inf", item->comment, &status);
+                        }
+                    } else {
+                        fits_update_key(fits->fd, TDOUBLE, item->name, &item->data.F64, item->comment,
+                                        &status);
+                    }
+                    break;
+                }
+            case PS_DATA_STRING:
+                fits_update_key(fits->fd, TSTRING, item->name, item->data.V, item->comment, &status);
+                break;
+            default:  // all other META types are ignored
+                psLogMsg(__func__, PS_LOG_WARN, "Attempt to write metadata type %x to FITS header ignored.\n",
+                         item->type);
+                break;
+            }
+        }
+
+        if (status != 0) {
+            char fitsErr[MAX_STRING_LENGTH];
+            (void)fits_get_errstatus(status, fitsErr);
+            psError(PS_ERR_IO, true, _("Could not write data to file. CFITSIO Error: %s"), fitsErr);
+            return false;
+        }
+    }
+
+    psFree(iter);
+
+    return true;
+}
+
+bool psFitsWriteHeader(psFits *fits,
+                       const psMetadata *output
+                      )
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_METADATA_NON_NULL(output, false);
+
+    return fitsWriteHeader(fits, output, true);
+}
+
+bool psFitsWriteBlank(psFits* fits,
+                      const psMetadata* output,
+                      const char *extname
+                     )
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+
+    // We allow output == NULL in order to write a minimal header.
+
+    // Create a dummy image HDU for the primary HDU
+    int status = 0;                 // Status of cfitsio
+
+    psFitsMoveLast(fits);
+
+    int hdus = psFitsGetSize(fits);     // Number of HDUs in file
+    if (hdus == 0) {
+        // We're creating the first image
+        fits_create_img(fits->fd, 16, 0, NULL, &status);
+    } else {
+        // Insert after the current position
+        fits_insert_img(fits->fd, 16, 0, NULL, &status);
+    }
+
+    if (status) {
+        char fitsErr[MAX_STRING_LENGTH];
+        (void)fits_get_errstatus(status, fitsErr);
+        psError(PS_ERR_IO, true, "Unable to create blank header.\n%s\n", fitsErr);
+        return false;
+    }
+
+    if (output && !fitsWriteHeader(fits, output, false)) {
+        psError(PS_ERR_IO, false, "Unable to write FITS header.\n");
+        return false;
+    }
+
+    if (extname && strlen(extname)) {
+        psFitsSetExtName(fits, extname);
+    }
+
+    char buffer[10];
+    fits_write_img(fits->fd, TSHORT, 1, 0, buffer, &status);
+
+    return true;
+}
+
+bool psFitsHeaderValidate(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    // Traverse the metadata list and inspect at each key
+    psListIterator* iter = psListIteratorAlloc(header->list, PS_LIST_HEAD, true); // Iterator
+    psMetadataItem* item;               // Item from iteration
+    bool valid = true;                  // Are all items valid?
+    while ((item = psListGetAndIncrement(iter))) {
+        if (item->type > PS_DATA_STRING) { // i.e., a non-primitive type
+            valid = false;
+        }
+
+        if (strlen(item->name) > 8) {
+            item->name[8] = '\0'; // truncate to 8 characters
+        }
+
+        fits_uppercase(item->name); // make uppercase
+
+        // now, let's see if CFITSIO thinks this is a good keyword...
+        int status = 0;                 // Status from cfitsio calls
+        if (fits_test_keyword(item->name,&status) != 0) {
+            valid = false;
+        }
+    }
+    psFree(iter);
+
+    return valid;
+}
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsHeader.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsHeader.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsHeader.h	(revision 22290)
@@ -0,0 +1,87 @@
+/* @file  psFitsHeader.h
+ * @brief Contains Fits header I/O routines
+ *
+ * @author Robert DeSonia, MHPCC
+ *
+ * @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-10-03 21:27:21 $
+ *
+ * Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PS_FITSHEADER_H
+#define PS_FITSHEADER_H
+
+/// @addtogroup FileIO Input/Output
+/// @{
+
+#include "psFits.h"
+#include "psMetadata.h"
+
+
+/// Determine whether the current HDU is an empty PHU with a single compressed image following.
+///
+/// In that case, what should be treated as an image PHU is technically an empty PHU with a binary table
+/// extension.  We test the current position, number of extensions, the FITS headers and presence of a
+/// following compressed image to determine if this is the case.  If so, the FITS file pointer is left
+/// pointing at the compressed image.
+bool psFitsCheckSingleCompressedImagePHU(const psFits *fits, ///< FITS file pointer
+                                         const psMetadata *header ///< Header, or NULL
+    );
+
+/// Parse a string read from the FITS header.
+
+/// Removes the quotes, and any trailing spaces.  NOTE: the returned string is NOT on the psLib memory system
+/// --- it's simply a hacked version of the input string.  Note also that the input string is MODIFIED.
+char *p_psFitsHeaderParseString(char *string ///< String to parse
+    );
+
+/** Reads the header of the current HDU.
+ *
+ *  @return psMetadata*   the header data
+ */
+psMetadata* psFitsReadHeader(
+    psMetadata* out,
+    ///< The psMetadata to add the header data.  If null, a new psMetadata is created.
+    const psFits* fits                 ///< the psFits object
+);
+
+/** Reads the header of all HDUs.  The current HDU is not changed.
+ *
+ *  @return psMetadata*      the header data set as a number of metadata entries
+ */
+psMetadata* psFitsReadHeaderSet(
+    psMetadata* out,                         ///< output metadata or NULL if new psMetadata is to be created.
+    const psFits* fits                       ///< the psFits object
+);
+
+/** Writes the values of the metadata to the current HDU header.
+ *  Doesn't check if the header has to be created.
+ *
+ * @return bool         if TRUE, the write was successful, otherwise FALSE.
+ */
+bool psFitsWriteHeader(
+    psFits* fits,                       ///< the psFits object
+    const psMetadata* output            ///< the psMetadata data in which to write
+);
+
+/** Writes a "blank" --- a header only, with no image or table.
+ *
+ *  @return bool        if TRUE, the write was successful, otherwise FALSE.
+ */
+bool psFitsWriteBlank(
+    psFits* fits,                       ///< the psFits object
+    const psMetadata* output,           ///< the psMetadata data in which to write
+    const char *extname
+);
+
+/** psFitsHeaderValidate validates the supplied header so that it is in
+ *  compliance to the FITS standard for header keyword names and types.
+ *
+ *  @return bool        TRUE if the resulting header conforms to the FITS
+ *                      standard, otherwise FALSE
+ */
+bool psFitsHeaderValidate(psMetadata *header);
+
+/// @}
+#endif // #ifndef PS_FITS_H
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsImage.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsImage.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsImage.c	(revision 22290)
@@ -0,0 +1,1097 @@
+/** @file  psFitsImage.c
+ *
+ *  @brief Contains Fits I/O routines
+ *
+ *  @ingroup FileIO
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.23 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-16 20:10:35 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+
+#include "psAbort.h"
+#include "psType.h"
+#include "psAssert.h"
+#include "psError.h"
+#include "psString.h"
+#include "psLogMsg.h"
+#include "psTrace.h"
+#include "psVector.h"
+#include "psRandom.h"
+#include "psImageStructManip.h"
+
+#include "psFits.h"
+#include "psFitsFloat.h"
+#include "psFitsFloatFile.h"
+#include "psFitsHeader.h"
+
+#include "psMemory.h"
+
+#include "psFitsImage.h"
+
+#define MAX_STRING_LENGTH 256           // maximum length string for FITS routines
+
+// Information required to read a FITS file
+typedef struct {
+    int nAxis;                          // Number of axes
+    int bitPix;                         // Bits per pixel
+    long nAxes[3];                      // Length of each axis
+    long firstPixel[3];                 // lower-left corner of image subset
+    long lastPixel[3];                  // upper-right corner of image subset
+    long increment[3];                  // increment for image subset
+    int fitsDatatype;                   // cfitsio data type
+    int psDatatype;                     // psLib data type
+} p_psFitsReadInfo;
+
+// Read the vital statistics of this FITS image, in preparation for reading the image
+static p_psFitsReadInfo *p_psFitsReadInfoAlloc(
+    const psFits *fits, // The FITS file handle
+    psRegion region, // Region to read
+    int z // z-plane to read in cube
+    )
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(z, NULL);
+
+    p_psFitsReadInfo *info = psAlloc(sizeof(p_psFitsReadInfo));
+    memset(info, 0, sizeof(p_psFitsReadInfo));
+
+    int status = 0;                     // CFITSIO status
+
+    // check to see if we even are positioned on an image HDU
+    int hduType;                        // Type of HDU
+    if (fits_get_hdu_type(fits->fd, &hduType, &status) != 0) {
+        psFitsError(status, true, "Could not determine the HDU type.");
+        goto bad;
+    }
+    if (hduType != IMAGE_HDU) {
+        psError(PS_ERR_IO, true, _("Current FITS HDU type must be an image."));
+        goto bad;
+    }
+
+    // Get the data type 'bitPix' from the FITS image
+    if (fits_get_img_equivtype(fits->fd, &info->bitPix, &status) != 0) {
+        psFitsError(status, true, "Could not determine image data type.");
+        goto bad;
+    }
+
+    /* Get the dimensions 'nAxis' from the FITS image */
+    if (fits_get_img_dim(fits->fd, &info->nAxis, &status) != 0) {
+        psFitsError(status, true, "Could not determine image dimensions.");
+        goto bad;
+    }
+
+    /* Validate the number of axis */
+    if ((info->nAxis < 2) || (info->nAxis > 3)) {
+        psError(PS_ERR_IO, true,
+                _("Image number of dimensions, %d, is not supported."), info->nAxis);
+        goto bad;
+    }
+
+    /* Get the Image size from the FITS file */
+    if (fits_get_img_size(fits->fd, info->nAxis, info->nAxes, &status) != 0) {
+        psFitsError(status, true, "Could not determine image size.");
+        goto bad;
+    }
+
+    info->firstPixel[0] = region.x0 + 1;
+    info->firstPixel[1] = region.y0 + 1;
+    info->firstPixel[2] = z + 1;
+
+    if (region.x1 > 0) {
+        info->lastPixel[0] = region.x1;
+    } else {
+        info->lastPixel[0] = info->nAxes[0] + region.x1; // n.b., region.x1 < 0
+    }
+    if (region.y1 > 0) {
+        info->lastPixel[1] = region.y1;
+    } else {
+        info->lastPixel[1] = info->nAxes[1] + region.y1; // n.b., region.y1 < 0
+    }
+    info->lastPixel[2] = z + 1;
+
+    info->increment[0] = 1;
+    info->increment[1] = 1;
+    info->increment[2] = 1;
+
+    // Check scale and zero
+    double bscale = 0.0, bzero = 0.0;    // Scale and zero point
+    if (fits_read_key_dbl(fits->fd, "BSCALE", &bscale, NULL, &status) && status != KEY_NO_EXIST) {
+        psFitsError(status, true, "Unable to read header.");
+        goto bad;
+    }
+    status = 0;
+    if (fits_read_key_dbl(fits->fd, "BZERO", &bzero, NULL, &status) && status != KEY_NO_EXIST) {
+        psFitsError(status, true, "Unable to read header.");
+        goto bad;
+    }
+    status = 0;
+
+    if ((bscale != 0.0 && bscale != 1.0) || bzero != (int)bzero) {
+        // It's a floating-point image that's been quantised
+        // cfitsio will apply the scale and zero point for us if we choose the correct data type
+        switch (info->bitPix) {
+          case BYTE_IMG:
+          case SBYTE_IMG:
+          case USHORT_IMG:
+          case SHORT_IMG:
+          case ULONG_IMG:
+          case LONG_IMG:
+          case FLOAT_IMG:
+            info->psDatatype = PS_TYPE_F32;
+            info->fitsDatatype = TFLOAT;
+            break;
+          case LONGLONG_IMG:
+          case DOUBLE_IMG:
+            info->psDatatype = PS_TYPE_F64;
+            info->fitsDatatype = TDOUBLE;
+            break;
+          default:
+            psError(PS_ERR_IO, true, _("FITS image type, BITPIX=%d, is not supported."), info->bitPix);
+            goto bad;
+        }
+    } else {
+        switch (info->bitPix) {
+          case BYTE_IMG:
+            info->psDatatype = PS_TYPE_U8;
+            info->fitsDatatype = TBYTE;
+            break;
+          case SBYTE_IMG:
+            info->psDatatype = PS_TYPE_S8;
+            info->fitsDatatype = TSBYTE;
+            break;
+          case USHORT_IMG:
+            info->psDatatype = PS_TYPE_U16;
+            info->fitsDatatype = TUSHORT;
+            break;
+          case SHORT_IMG:
+            info->psDatatype = PS_TYPE_S16;
+            info->fitsDatatype = TSHORT;
+            break;
+          case ULONG_IMG:
+            info->psDatatype = PS_TYPE_U32;
+            info->fitsDatatype = TUINT;
+            break;
+          case LONG_IMG:
+            info->psDatatype = PS_TYPE_S32;
+            info->fitsDatatype = TINT;
+            break;
+          case LONGLONG_IMG:
+            info->psDatatype = PS_TYPE_S64;
+            info->fitsDatatype = TLONGLONG;
+            break;
+          case FLOAT_IMG:
+            info->psDatatype = PS_TYPE_F32;
+            info->fitsDatatype = TFLOAT;
+            break;
+          case DOUBLE_IMG:
+            info->psDatatype = PS_TYPE_F64;
+            info->fitsDatatype = TDOUBLE;
+            break;
+          default:
+            psError(PS_ERR_IO, true, _("FITS image type, BITPIX=%d, is not supported."), info->bitPix);
+            goto bad;
+        }
+    }
+    return info;
+
+    // Common error cleanup
+bad:
+    psFree(info);
+    return NULL;
+}
+
+
+bool psFitsImageSize(int *numCols, int *numRows, psElemType *type, const psFits *fits, psRegion region)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    if (psFitsCheckSingleCompressedImagePHU(fits, NULL)) {
+        // This is really what we want, not the empty PHU
+        psTrace("psLib.fits", 1,
+                "This PHU should really be a compressed image --- reading that image instead.");
+    }
+
+    p_psFitsReadInfo *info = p_psFitsReadInfoAlloc(fits, region, 0); // How big the region to read is
+
+    if (numCols) {
+        *numCols = info->lastPixel[0] - info->firstPixel[0] + 1;
+    }
+    if (numRows) {
+        *numRows = info->lastPixel[1] - info->firstPixel[1] + 1;
+    }
+    if (type) {
+        *type = info->psDatatype;
+    }
+
+    psFree(info);
+
+    return true;
+}
+
+# if (0)
+// XXX this needs to be optional (eg, invalid for a mask)
+
+// Apply the BSCALE and BZERO for an image with a "fuzz", so that we get the image as it should be written to
+// disk.
+// The idea is that the "fuzz" (adding a random number between 0 and 1) preserves the expectation value of
+// the image (e.g., a value of 0.1 will get translated to zero 90% of the time, and unity 10% of the time),
+// though at the cost of adding an additional variance of 1/12 (a standard deviation of ~0.29).
+static psImage *scaleImageForDisk(psImage *image, // Image to which to apply BSCALE and BZERO
+                                  int bitpix, // Output BITPIX
+                                  double bscale, // Scaling
+                                  double bzero, // Zero point
+                                  psRandom *rng // Random number generator (for the "fuzz"), or NULL
+                                  )
+{
+    assert(image);
+
+    if (!PS_IS_PSELEMTYPE_REAL(image->type.type) || bitpix == 0) {
+        return psMemIncrRefCounter(image);
+    }
+
+    psElemType outType;                 // Type for output image
+    // Choosing to use signed types because those don't require BSCALE,BZERO to represent them in the FITS
+    // file
+    switch (bitpix) {
+      case 8:
+        outType = PS_TYPE_S8;
+        break;
+      case 16:
+        outType = PS_TYPE_S16;
+        break;
+      case 32:
+        outType = PS_TYPE_S32;
+        break;
+      case 64:
+        outType = PS_TYPE_S64;
+        break;
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Target bitpix (%d) is not one of 8,16,32,64", bitpix);
+        return NULL;
+    }
+
+    if (bscale == 1.0 && bzero == 0.0) {
+        return psImageCopy(NULL, image, outType);
+    }
+
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+    psImage *out = psImageAlloc(numCols, numRows, outType); // Output image
+
+    if (!psMemIncrRefCounter(rng)) {
+        // Don't blab about which seed we're going to get --- it's not necessary for this purpose
+        psU64 seed = p_psRandomGetSystemSeed(false);
+        rng = psRandomAlloc(PS_RANDOM_TAUS, seed);
+    }
+
+
+#define SCALE_WRITE_OUT_CASE(IN, INTYPE, OUT, OUTTYPE) \
+    case PS_TYPE_##OUTTYPE: { \
+        ps##INTYPE scale = 1.0 / bscale; \
+        ps##INTYPE zero = bzero; \
+        for (int y = 0; y < numRows; y++) { \
+            for (int x = 0; x < numCols; x++) { \
+                /* Add random factor [0,1): adds a variance of 1/12, but preserves the expectation value */ \
+                ps##INTYPE random = psRandomUniform(rng); \
+                (OUT)->data.OUTTYPE[y][x] = ((IN)->data.INTYPE[y][x] - zero) * scale + random; \
+            } \
+        } \
+        break; \
+    }
+
+#define SCALE_WRITE_IN_CASE(IN, INTYPE, OUT) \
+    case PS_TYPE_##INTYPE: { \
+        switch (outType) { \
+            SCALE_WRITE_OUT_CASE(IN, INTYPE, OUT, S8); \
+            SCALE_WRITE_OUT_CASE(IN, INTYPE, OUT, S16); \
+            SCALE_WRITE_OUT_CASE(IN, INTYPE, OUT, S32); \
+            SCALE_WRITE_OUT_CASE(IN, INTYPE, OUT, S64); \
+          default: \
+            psAbort("Should be unreachable."); \
+        } \
+        break; \
+    }
+
+    switch (image->type.type) {
+        SCALE_WRITE_IN_CASE(image, F32, out);
+        SCALE_WRITE_IN_CASE(image, F64, out);
+      default:
+        psAbort("Should be unreachable.");
+    }
+
+    psFree(rng);
+
+    return out;
+}
+# endif
+
+# if (0)
+// XXX supporting code needs to make this an optional operation
+// Determine BSCALE and BZERO for an image, and generate a new image with it applied
+// TRUE = BZERO + BSCALE * FITS
+static psImage *scaleImageDetermine(double *bscale, // Scaling, to return
+                                    double *bzero, // Zero point, to return
+                                    psImage *image, // Image to scale
+                                    int bitpix, // Desired bits per pixel
+                                    psRandom *rng // Random number generator for scaleImageForDisk
+                                    )
+{
+    PS_ASSERT_PTR_NON_NULL(bscale, NULL);
+    PS_ASSERT_PTR_NON_NULL(bzero, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE_F32_OR_F64(image, NULL);
+
+    *bscale = 0.0;
+    *bzero = 0.0;
+
+    switch (bitpix) {
+      case 0:
+        // No scaling applied
+        return psMemIncrRefCounter(image);
+      case 8:
+      case 16:
+      case 32:
+      case 64:
+        // Nothing to do; just allowing these values to pass through
+        break;
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Target bitpix (%d) is not one of 8,16,32,64", bitpix);
+        return NULL;
+    }
+
+    int numCols = image->numCols, numRows = image->numRows;
+    double range = pow(2.0, bitpix); // Range of values for target BITPIX
+
+#define SCALE_DETERMINE_CASE(IN, INTYPE) \
+    case PS_TYPE_##INTYPE: { \
+        ps##INTYPE min = INFINITY, max = -INFINITY; /* Minimum and maximum values */ \
+        for (int y = 0; y < numRows; y++) { \
+            for (int x = 0; x < numCols; x++) { \
+                ps##INTYPE value = (IN)->data.INTYPE[y][x]; /* Value of interest */ \
+                if (isfinite(value)) { \
+                    if (value < min) { \
+                        min = value; \
+                    } \
+                    if (value > max) { \
+                        max = value; \
+                    } \
+                } \
+            } \
+        } \
+        if (!isfinite(min) || !isfinite(max)) { \
+            psWarning("No valid values in image to derive BSCALE,BZERO --- using original image."); \
+            *bscale = 1.0; \
+            *bzero = 0.0; \
+            return psMemIncrRefCounter(image); \
+        } \
+        if (min == max) { \
+            *bscale = 1.0; \
+            *bzero = min; \
+        } else { \
+            *bscale = (max - min) / (range - 1.0); \
+            *bzero = min + 0.5 * range * (*bscale); \
+        } \
+        break; \
+    }
+
+    switch (image->type.type) {
+        SCALE_DETERMINE_CASE(image, F32);
+        SCALE_DETERMINE_CASE(image, F64);
+      default:
+        psAbort("Should be unreachable.");
+    }
+    psTrace("psLib.fits", 3, "BSCALE = %.10lf, BZERO = %.10lf\n", *bscale, *bzero);
+
+    return scaleImageForDisk(image, bitpix, *bscale, *bzero, rng);
+}
+# endif
+
+#if 0
+// This function to apply BSCALE and BZERO to an image read immediately from disk should not be necessary at
+// the present time, since cfitsio should apply the scaling itself in the process of reading.  However, we may
+// later desire it.
+static psImage *scaleImageFromDisk(psFits *fits, psImage *image)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+
+    if (bscale == 0.0) {
+        // BSCALE = 0 means don't apply anything
+        return psMemIncrRefCounter(image);
+    }
+
+    psElemType inType = image->type.type; // Type for input image
+    psElemType outType;                 // Type for output image
+    switch (inType) {
+      case PS_TYPE_S8:
+      case PS_TYPE_S16:
+      case PS_TYPE_S32:
+      case PS_TYPE_U8:
+      case PS_TYPE_U16:
+        outType = PS_TYPE_F32;
+        break;
+      case PS_TYPE_S64:
+      case PS_TYPE_U32:
+      case PS_TYPE_U64:
+        outType = PS_TYPE_F64;
+        break;
+        // Including floating-point types just in case someone wants to apply a BSCALE and BZERO to them.
+      case PS_TYPE_F32:
+        outType = PS_TYPE_F32;
+        break;
+      case PS_TYPE_F64:
+        outType = PS_TYPE_F64;
+        break;
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unsupported image type: %x", inType);
+        return NULL;
+    }
+
+    int numCols = image->numCols, numRows = image->numRows;
+    psImage *out = psImageAlloc(numCols, numRows, outType); // Output scaled image
+
+
+#define SCALE_READ_OUT_CASE(INTYPE, OUTTYPE) \
+  case PS_TYPE_##OUTTYPE: { \
+      for (int y = 0; y < numRows; y++) { \
+          for (int x = 0; x < numCols; x++) { \
+              out->data.OUTTYPE[y][x] = image->data.INTYPE[y][x] * bscale + bzero; \
+          } \
+      } \
+      break; \
+  }
+
+#define SCALE_READ_IN_CASE(INTYPE) \
+  case PS_TYPE_##INTYPE: { \
+      switch (outType) { \
+          SCALE_READ_OUT_CASE(INTYPE, F32); \
+          SCALE_READ_OUT_CASE(INTYPE, F64); \
+        default: \
+          psAbort("Should never get here: type %x should be F32 or F64", outType); \
+      } \
+      break; \
+  }
+
+    switch (inType) {
+        SCALE_READ_IN_CASE(S8);
+        SCALE_READ_IN_CASE(S16);
+        SCALE_READ_IN_CASE(S32);
+        SCALE_READ_IN_CASE(S64);
+        SCALE_READ_IN_CASE(U8);
+        SCALE_READ_IN_CASE(U16);
+        SCALE_READ_IN_CASE(U32);
+        SCALE_READ_IN_CASE(U64);
+        SCALE_READ_IN_CASE(F32);
+        SCALE_READ_IN_CASE(F64);
+      default:
+          psAbort("Should never get here: type %x should be integer", inType);
+    }
+
+    return out;
+}
+#endif
+
+// Convert an image to the desired BITPIX, i.e., the desired disk representation
+static psImage *imageToDiskRepresentation(double *bscale, // Scaling applied
+                                          double *bzero, // Zero point applied
+                                          psFitsFloat *floatType, // Type of custom floating-point
+                                          psFits *fits, // FITS file pointer
+                                          const psImage *image, // Current type
+                                          psRandom *rng, // Random number generator
+                                          bool newScaleZero // Determine a new BSCALE and BZERO?
+                                          )
+{
+    assert(bscale);
+    assert(bzero);
+    assert(floatType);
+    assert(fits);
+    assert(image);
+
+    *bscale = 1.0;
+    *bzero = 0.0;
+    *floatType = PS_FITS_FLOAT_NONE;
+
+    // Custom floating-point
+    if (PS_IS_PSELEMTYPE_REAL(image->type.type) && fits->conventions.psBitpix &&
+        fits->floatType != PS_FITS_FLOAT_NONE) {
+        *floatType = fits->floatType;
+        return psFitsFloatImageToDisk(image, fits->floatType);
+    }
+
+    // Automatically select what we're given
+    if (fits->bitpix == 0) {
+        return psMemIncrRefCounter((psImage*)image); // Casting away const
+    }
+
+    // Quantise floating-point images
+    // XXX this needs to be more controlled: certainly not valid for output masks!
+    # if (0)
+    if (PS_IS_PSELEMTYPE_REAL(image->type.type) && fits->bitpix > 0) {
+        if (newScaleZero) {
+            return scaleImageDetermine(bscale, bzero, (psImage*)image, fits->bitpix, rng);
+        }
+        // Get the current BSCALE and BZERO
+        int status = 0;                 // Status of cfitsio
+        if (fits_read_key_dbl(fits->fd, "BSCALE", bscale, NULL, &status) && status != KEY_NO_EXIST) {
+            psFitsError(status, true, "Unable to read header.");
+            return NULL;
+        }
+        status = 0;
+        if (fits_read_key_dbl(fits->fd, "BZERO", bzero, NULL, &status) && status != KEY_NO_EXIST) {
+            psFitsError(status, true, "Unable to read header.");
+            return NULL;
+        }
+        status = 0;
+        if (*bscale == 0.0) {
+            psError(PS_ERR_IO, true,
+                    "Supposed to use old values of BSCALE and BZERO, but they don't exist.");
+            return NULL;
+        }
+        return scaleImageForDisk((psImage*)image, fits->bitpix, *bscale, *bzero, rng);
+    }
+    # endif
+
+    // Choose the appropriate output type, given the input type and desired bits per pixel
+#define CONVERT_TYPE_INT_CASE(OUTTYPE, INTYPE, BITPIX) \
+  case BITPIX: \
+    OUTTYPE = PS_IS_PSELEMTYPE_UNSIGNED(INTYPE) ? PS_TYPE_U##BITPIX : PS_TYPE_S##BITPIX; \
+    break;
+#define CONVERT_TYPE_FLOAT_CASE(OUTTYPE, BITPIX) \
+  case -BITPIX: /* Note the use of the negative sign */ \
+    OUTTYPE = PS_TYPE_F##BITPIX; \
+    break;
+
+    *bscale = 1.0;
+    *bzero = 0.0;
+    psElemType inType = image->type.type; // Type for input image
+    psElemType outType;                 // Type for output image
+    switch (fits->bitpix) {
+        CONVERT_TYPE_INT_CASE(outType, inType, 8);
+        CONVERT_TYPE_INT_CASE(outType, inType, 16);
+        CONVERT_TYPE_INT_CASE(outType, inType, 32);
+        CONVERT_TYPE_INT_CASE(outType, inType, 64);
+        CONVERT_TYPE_FLOAT_CASE(outType, 32);
+        CONVERT_TYPE_FLOAT_CASE(outType, 64);
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Target bitpix (%d) is not one of 8,16,32,64",
+                fits->bitpix);
+        return NULL;
+    }
+
+    if (outType == inType) {
+        return psMemIncrRefCounter((psImage*)image);
+    }
+    return psImageCopy(NULL, image, outType);
+}
+
+
+// Read into an extant image of just the right size
+static bool fitsReadImage(psImage *output,   // Output image
+                          const psFits *fits, // FITS file handle
+                          p_psFitsReadInfo *info // Info on how to read
+                         )
+{
+    // n.b., this assumes contiguous image buffer
+    assert(output);
+    assert(fits);
+    assert(info);
+    assert(output->numCols == info->lastPixel[0] - info->firstPixel[0] + 1);
+    assert(output->numRows == info->lastPixel[1] - info->firstPixel[1] + 1); // Right size
+    assert(!output->parent);            // No parents means the buffer is contiguous
+
+    int anynull = 0;                    // Are there any NULLs in the data?
+    int status = 0;                     // cfitsio status
+    if (fits_read_subset(fits->fd, info->fitsDatatype, info->firstPixel, info->lastPixel,
+                         info->increment, NULL, output->data.V[0], &anynull, &status) != 0) {
+        psFitsError(status, true, "Reading FITS file failed.");
+        return false;
+    }
+
+    // No need to apply the BSCALE, BZERO because cfitsio does this for us
+
+    return true;
+}
+
+psImage* psFitsReadImage(const psFits *fits, // the psFits object
+                         psRegion region, // the region in the FITS image to read
+                         int z          // the z-plane in the FITS image cube to read
+                        )
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(z, NULL);
+
+    if (psFitsCheckSingleCompressedImagePHU(fits, NULL)) {
+        // This is really what we want, not the empty PHU
+        psTrace("psLib.fits", 1,
+                "This PHU should really be a compressed image --- reading that image instead.");
+    }
+
+    p_psFitsReadInfo *info = p_psFitsReadInfoAlloc(fits, region, z);
+
+    // Size of image
+    int numCols = info->lastPixel[0] - info->firstPixel[0] + 1;
+    int numRows = info->lastPixel[1] - info->firstPixel[1] + 1;
+
+    psImage *inImage = psImageAlloc(numCols, numRows, info->psDatatype); // Image to read in
+
+    psFitsFloat floatType = psFitsFloatImageCheck(fits); // Type of custom floating-point
+    psImage *outImage = (floatType == PS_FITS_FLOAT_NONE ? psMemIncrRefCounter(inImage) :
+                         psImageAlloc(numCols, numRows, psFitsFloatImageType(floatType))); // Output image
+
+    if (!fitsReadImage(inImage, fits, info)) {
+        psFree(info);
+        psFree(inImage);
+        return NULL;
+    }
+    psFree(info);
+
+    if (floatType != PS_FITS_FLOAT_NONE) {
+        outImage = psFitsFloatImageFromDisk(outImage, inImage, floatType);
+    }
+    psFree(inImage);
+
+    return outImage;
+}
+
+psImage* psFitsReadImageBuffer(psImage *outImage, // Output image buffer
+                               const psFits *fits, // the psFits object
+                               psRegion region, // the region in the FITS image to read
+                               int z           // the z-plane in the FITS image cube to read
+                              )
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(z, NULL);
+
+    if (outImage && outImage->parent) {
+        psError(PS_ERR_IO, true, "Unable to read into a buffer for a child image.\n");
+        return NULL;
+    }
+
+    if (psFitsCheckSingleCompressedImagePHU(fits, NULL)) {
+        // This is really what we want, not the empty PHU
+        psTrace("psLib.fits", 1,
+                "This PHU should really be a compressed image --- reading that image instead.");
+    }
+
+    p_psFitsReadInfo *info = p_psFitsReadInfoAlloc(fits, region, z);
+
+    // Size of image
+    int numCols = info->lastPixel[0] - info->firstPixel[0] + 1;
+    int numRows = info->lastPixel[1] - info->firstPixel[1] + 1;
+
+    psFitsFloat floatType = psFitsFloatImageCheck(fits); // Type of custom floating-point
+    psImage *inImage;                   // Image to read in
+    if (floatType == PS_FITS_FLOAT_NONE) {
+        inImage = psImageRecycle(outImage, numCols, numRows, info->psDatatype);
+        outImage = psMemIncrRefCounter(inImage);
+    } else {
+        inImage = psImageAlloc(numCols, numRows, info->psDatatype);
+        outImage = psImageRecycle(outImage, numCols, numRows, psFitsFloatImageType(floatType));
+    }
+
+    if (!fitsReadImage(inImage, fits, info)) {
+        psFree(info);
+        psFree(inImage);
+        psFree(outImage);
+        return NULL;
+    }
+    psFree(info);
+
+    if (floatType != PS_FITS_FLOAT_NONE) {
+        outImage = psFitsFloatImageFromDisk(outImage, inImage, floatType);
+    }
+    psFree(inImage);
+
+    return outImage;
+}
+
+bool psFitsWriteImage(psFits* fits,
+                      psMetadata* header,
+                      const psImage* input,
+                      int numZPlanes,
+                      const char* extname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_IMAGE_NON_NULL(input, false);
+    // this is equivalent to insert after the last HDU
+
+    psFitsMoveLast(fits);
+    return psFitsInsertImage(fits,header,input,numZPlanes,extname,true);
+}
+
+bool psFitsInsertImage(psFits* fits, psMetadata* header, const psImage* image, int numZPlanes,
+                       const char* extname, bool after)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+
+    int numCols = image->numCols;       // Number of columns for image
+    int numRows = image->numRows;       // Number of rows for image
+    int status = 0;                     // Status from cfitsio
+
+    double bscale = 0.0, bzero = 0.0;   // Scale and zero point to put in header (*already* applied to data)
+    psFitsFloat floatType;              // Custom floating-point convention type
+    psImage *diskImage = imageToDiskRepresentation(&bscale, &bzero, &floatType, fits, image,
+                                                   NULL, true); // Image to write out
+    if (!diskImage) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to convert image to desired disk format.");
+        return false;
+    }
+
+    // determine the FITS-equivalent parameters
+    int bitPix;                         // Bits per pixel
+    double cfitsioBzero = 0.0;          // Zero point for cfitsio to apply
+    int dataType;                       // cfitsio data type
+    if (!p_psFitsTypeToCfitsio(diskImage->type.type, &bitPix, &cfitsioBzero, &dataType)) {
+        psFree(diskImage);
+        return false;
+    }
+    if (cfitsioBzero != 0.0) {
+        // p_psFitsTypeToCfitsio and imageToDiskRepresentation must not clash!
+        assert(bzero == 0.0 && bscale == 1.0);
+        bscale = 1.0;
+        bzero = cfitsioBzero;
+    }
+    assert(bitPix == fits->bitpix || fits->bitpix == 0);
+
+    int naxis = 3;                      // Number of axes
+    long naxes[3];                      // Length of each axis
+    naxes[0] = numCols;
+    naxes[1] = numRows;
+    naxes[2] = numZPlanes;
+
+    if (numZPlanes < 2) {
+        naxis = 2;
+    }
+
+    // Create the image HDU
+    int hdus = psFitsGetSize(fits);     // Number of HDUs in file
+    if (hdus == 0) {
+        // We're creating the first image
+        fits_create_img(fits->fd, bitPix, naxis, naxes, &status);
+    } else {
+        if (!after) {
+            if (psFitsGetExtNum(fits) == 0) {
+                // We're creating a replacement primary HDU.
+                // Set status to signal fits_insert_img to insert a new primary HDU
+                status = PREPEND_PRIMARY;
+            } else {
+                // Move back one to perform an insert after the previous HDU
+                psFitsMoveExtNum(fits, -1, true);
+            }
+        }
+        // Insert after the current position
+        fits_insert_img(fits->fd, bitPix, naxis, naxes, &status);
+    }
+
+    // write the header, if any.
+    if (header && !psFitsWriteHeader(fits, header)) {
+        psError(PS_ERR_IO, false, "Unable to write FITS header.\n");
+        psFree(diskImage);
+        return false;
+    }
+
+    // We only want cfitsio to do the scale and zero if the type conversion requires it (e.g., input type is
+    // an unsigned integer type).  In all other cases, we have already converted the image to use the
+    // appropriate scale and zero (because we want to apply a randomiser to the quantisation).
+    fits_set_bscale(fits->fd, 1.0, bzero, &status);
+
+    if (bscale != 0.0) {
+        fits_write_key_dbl(fits->fd, "BZERO", bzero, 12,
+                           "Scaling: TRUE = BZERO + BSCALE * DISK", &status);
+        fits_write_key_dbl(fits->fd, "BSCALE", bscale, 12,
+                           "Scaling: TRUE = BZERO + BSCALE * DISK", &status);
+        if (psFitsError(status, true, "Could not write BSCALE/BZERO headers to file.")) {
+            psFree(diskImage);
+            return false;
+        }
+    }
+
+    if (floatType != PS_FITS_FLOAT_NONE) {
+        psFitsFloatImageSet(fits, floatType);
+    }
+
+    if (extname && strlen(extname) > 0) {
+        psFitsSetExtName(fits, extname);
+    }
+
+    if (image->parent == NULL) {
+        // if no parent, assume that the image data is contiguous
+        fits_write_img(fits->fd, dataType, 1, numCols*numRows, diskImage->data.V[0], &status);
+    } else {
+        // image data may not be contiguous; write one row at a time
+        int firstPixel = 1;
+        for (int row = 0; row < numRows; row++) {
+            fits_write_img(fits->fd, dataType, firstPixel, numCols, diskImage->data.V[row], &status);
+            firstPixel += numCols;
+        }
+    }
+
+    psFree(diskImage);
+
+    if (psFitsError(status, true, "Could not write image to file.")) {
+        return false;
+    }
+
+    return true;
+
+}
+
+bool psFitsUpdateImage(psFits* fits, const psImage* input, int x0, int y0, int z)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_IMAGE_NON_NULL(input, false);
+
+    int status = 0;
+
+    // check to see if we are positioned on an image HDU
+    int hduType;
+    if (fits_get_hdu_type(fits->fd, &hduType, &status) != 0) {
+        psFitsError(status, true, "Could not determine the HDU type.");
+        return NULL;
+    }
+    if (hduType != IMAGE_HDU) {
+        psError(PS_ERR_IO, true, _("Current FITS HDU type must be an image."));
+        return NULL;
+    }
+
+    int numCols = input->numCols;
+    int numRows = input->numRows;
+
+    double bscale = 0.0, bzero = 0.0;   // Scale and zero point to put in header (*already* applied to data)
+    psFitsFloat floatType;              // Custom floating-point convention type
+    psImage *diskImage = imageToDiskRepresentation(&bscale, &bzero, &floatType, fits, input,
+                                                   NULL, false); // Image to write out
+    if (!diskImage) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to convert image to desired disk format.");
+        return false;
+    }
+
+    // determine the FITS-equivalent parameters
+    int bitPix;                         // Bits per pixel
+    double cfitsioBzero = 0.0;          // Zero point for cfitsio to apply
+    int dataType;                       // cfitsio data type
+    if (!p_psFitsTypeToCfitsio(diskImage->type.type, &bitPix, &cfitsioBzero, &dataType)) {
+        psFree(diskImage);
+        return false;
+    }
+    if (cfitsioBzero != 0.0) {
+        // p_psFitsTypeToCfitsio and imageToDiskRepresentation must not clash!
+        assert(bzero == 0.0 && bscale == 1.0);
+        bscale = 1.0;
+        bzero = cfitsioBzero;
+    }
+    assert(bitPix == fits->bitpix || fits->bitpix == 0);
+
+    //check to see if the HDU has the same datatype
+    int fileBitpix;
+    int naxis;
+    long nAxes[3];
+    nAxes[2] = 1;
+    fits_get_img_param(fits->fd, 3, &fileBitpix, &naxis, nAxes, &status);
+
+    //check to see if the HDU has the same datatype
+    if (bitPix != fileBitpix) {
+        char* fitsTypeStr;
+        char* imageTypeStr;
+        PS_TYPE_NAME(fitsTypeStr, fileBitpix);
+        PS_TYPE_NAME(imageTypeStr, input->type.type);
+        psError(PS_ERR_IO, true, _("Can not update a %s image given a %s image."), fitsTypeStr, imageTypeStr);
+        psFree(diskImage);
+        return false;
+    }
+
+    //check if the HDU has the z-plane requested
+    if (z >= nAxes[2]) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                _("Current FITS HDU has %ld z-planes, but z-plane %d was specified."), nAxes[2], z);
+        psFree(diskImage);
+        return false;
+    }
+
+    // determine the region in the FITS file domain
+    long firstPixel[3];
+    long lastPixel[3];
+
+    firstPixel[0] = x0 + 1;
+    firstPixel[1] = y0 + 1;
+    firstPixel[2] = z + 1;
+
+    lastPixel[0] = x0 + numCols;
+    lastPixel[1] = y0 + numRows;
+    lastPixel[2] = z + 1;
+
+    if (firstPixel[0] < 1 || firstPixel[0] > nAxes[0] ||
+        firstPixel[1] < 1 || firstPixel[1] > nAxes[1] ||
+        lastPixel[0] < 1 || lastPixel[0] > nAxes[0] ||
+        lastPixel[1] < 1 || lastPixel[1] > nAxes[1]) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Input image [size of %ix%i] at position (%i,%i) does not all lay in the %lix%li FITS image.",
+                numCols, numRows, x0, y0, nAxes[0], nAxes[1]);
+        psFree(diskImage);
+        return false;
+    }
+
+    // We only want cfitsio to do the scale and zero if the type conversion requires it (e.g., input type is
+    // an unsigned integer type).  In all other cases, we have already converted the image to use the
+    // appropriate scale and zero (because we want to apply a randomiser to the quantisation).
+    fits_set_bscale(fits->fd, 1.0, cfitsioBzero, &status);
+
+    fits_write_subset(fits->fd, dataType, firstPixel, lastPixel, diskImage->data.V[0], &status);
+
+    psFree(diskImage);
+
+    if (psFitsError(status, true, "Could not write data to file.")) {
+        return false;
+    }
+
+    return true;
+}
+
+psArray *psFitsReadImageCube(const psFits *fits, psRegion region)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    int nAxis = 0;                      // Number of axes
+    long nAxes[3];                      // Number of pixels on each axis
+    int status = 0;                     // cfitsio status value
+
+    // Some of this replicates what is in psFitsReadImage, so it's a little inefficient.  But it saves
+    // code replication, and should be sufficient for our needs.
+
+    if (psFitsCheckSingleCompressedImagePHU(fits, NULL)) {
+        // This is really what we want, not the empty PHU
+        psTrace("psLib.fits", 1,
+                "This PHU should really be a compressed image --- reading that image instead.");
+    }
+
+    if (fits_get_img_dim(fits->fd, &nAxis, &status) != 0) {
+        psFitsError(status, true, "Could not determine image dimensions.");
+        return NULL;
+    }
+
+    if (nAxis == 2) {
+        psArray *images = psArrayAlloc(1); // Single image plane
+        images->data[0] = psFitsReadImage(fits, region, 0);
+        return images;
+    }
+    if (nAxis == 3) {
+        if (fits_get_img_size(fits->fd, nAxis, nAxes, &status) != 0) {
+            psFitsError(status, true, "Could not determine image size.");
+            return NULL;
+        }
+
+        psArray *images = psArrayAlloc(nAxes[2]); // Array of image planes
+        for (int i = 0; i < nAxes[2]; i++) {
+            images->data[i] = psFitsReadImage(fits, region, i);
+        }
+
+        return images;
+    }
+
+    // Bad dimensionality
+    psError(PS_ERR_IO, true, _("Image number of dimensions, %d, is not supported."), nAxis);
+    return NULL;
+}
+
+bool psFitsWriteImageCube(psFits *fits, psMetadata *header, const psArray *input, const char *extname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_ARRAY_NON_NULL(input, false);
+
+    if (input->n == 0) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, _("The input array was empty."));
+        return false;
+    }
+
+    if (input->n == 1) {
+        // The problem reduces to one already solved
+        return psFitsWriteImage(fits, header, input->data[0], 1, extname);
+    }
+
+    // Check that all images are of the same size
+    psImage *testImage = input->data[0];// First image off the array
+    int numCols = testImage->numCols;   // Number of columns
+    int numRows = testImage->numRows;   // Number of rows
+    for (int i = 1; i < input->n; i++) {
+        testImage = input->data[i];
+        if (testImage->numCols != numCols || testImage->numRows != numRows) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true, _("The sizes of images in the array differ."));
+            return false;
+        }
+    }
+
+    // Need to check the header to make sure NAXIS and NAXIS[1-3] are correct
+    psMetadata *headerCopy = NULL;      // Copy of header
+    if (header) {
+        headerCopy = psMemIncrRefCounter(header);
+    } else {
+        headerCopy = psMetadataAlloc();
+    }
+    bool update = psMetadataAddS32(headerCopy, PS_LIST_HEAD, "NAXIS", PS_META_REPLACE, "Dimensionality", 3) &&
+        psMetadataAddS32(headerCopy, PS_LIST_HEAD, "NAXIS1", PS_META_REPLACE, "Number of columns", numCols) &&
+        psMetadataAddS32(headerCopy, PS_LIST_HEAD, "NAXIS2", PS_META_REPLACE, "Number of rows", numRows) &&
+        psMetadataAddS32(headerCopy, PS_LIST_HEAD, "NAXIS3", PS_META_REPLACE, "Number of image planes",
+                         input->n);
+    if (! update) {
+        psError(PS_ERR_UNKNOWN, false, _("Failed to add metadata item, %s."),
+                "NAXIS, NAXIS1, NAXIS2, NAXIS3");
+        psFree(headerCopy);
+        return false;
+    }
+
+    // Now we can safely write the images out.
+    // The first is an psFitsImageWrite to create the extension.
+    // The next are psFitsImageUpdate to write into the extension.
+    if (! psFitsWriteImage(fits, headerCopy, input->data[0], input->n, extname)) {
+        psError(PS_ERR_UNKNOWN, false, _("Could not write image plane %d."), 0);
+        psFree(headerCopy);
+        return false;
+    }
+    psFree(headerCopy);                 // Free, or drop reference
+
+    for (int i = 1; i < input->n; i++) {
+        if (! psFitsUpdateImage(fits, input->data[i], 0, 0, i)) {
+            psError(PS_ERR_UNKNOWN, false, _("Could not write image plane %d."), i);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool psFitsUpdateImageCube(psFits *fits, const psArray *input, int x0, int y0)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_ARRAY_NON_NULL(input, false);
+
+    if (input->n == 0) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, _("The input array was empty."));
+        return false;
+    }
+
+    for (int i = 0; i < input->n; i++) {
+        if (! psFitsUpdateImage(fits, input->data[i], x0, y0, i)) {
+            psError(PS_ERR_UNKNOWN, false, _("Could not update image plane %d."), i);
+            return false;
+        }
+    }
+
+    return true;
+}
+
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsImage.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsImage.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsImage.h	(revision 22290)
@@ -0,0 +1,93 @@
+/* @file  psFitsImage.h
+ * @brief Contains Fits I/O routines
+ *
+ * @author Robert DeSonia, MHPCC
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-16 01:04:56 $
+ * Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PS_FITSIMAGE_H
+#define PS_FITSIMAGE_H
+
+/// @addtogroup FileIO Input/Output
+/// @{
+
+#include "psFits.h"
+#include "psType.h"
+#include "psArray.h"
+#include "psVector.h"
+#include "psMetadata.h"
+#include "psImage.h"
+
+/// Return the dimensions and type of the FITS image
+bool psFitsImageSize(int *numCols, int *numRows, ///< Size of image
+                     psElemType *type,  ///< Type of image
+                     const psFits *fits, ///< FITS file pointer
+                     psRegion region    ///< Region in the FITS image to read
+    );
+
+/** Reads an image, given the desired region and z-plane.
+ *
+ *  @return psImage*     the read image or NULL if there was an error.
+ */
+psImage* psFitsReadImage(
+    const psFits* fits,                ///< the psFits object
+    psRegion region,                   ///< the region in the FITS image to read
+    int z                              ///< the z-plane in the FITS image cube to read
+);
+
+// Read an image into an extant buffer
+psImage* psFitsReadImageBuffer(psImage *output, // Output image buffer
+                               const psFits *fits,    // the psFits object
+                               psRegion region, // the region in the FITS image to read
+                               int z           // the z-plane in the FITS image cube to read
+                              );
+
+/** Writes an image, given the desired region and z-plane.
+ *
+ *  @return bool        TRUE is the write was successful, otherwise FALSE.
+ */
+bool psFitsWriteImage(
+    psFits* fits,                      ///< the psFits object
+    psMetadata* header,                 ///< header items for the new HDU.  Can be NULL.
+    const psImage* input,              ///< the image to output
+    int depth,                         ///< the number of z-planes of the FITS image data cube
+    const char* extname                ///< FITS extension name
+);
+
+/** Writes an image, given the desired region and z-plane.  A new IMAGE HDU is
+ *  appended to the end of the FITS file.
+ *
+ *  @return bool        TRUE is the write was successful, otherwise FALSE.
+ */
+bool psFitsInsertImage(
+    psFits* fits,                      ///< the psFits object
+    psMetadata* header,                 ///< header items for the new HDU.  Can be NULL.
+    const psImage* input,              ///< the image to output
+    int depth,                         ///< the number of z-planes of the FITS image data cube
+    const char* extname,               ///< FITS extension name
+    bool after                         ///< if TRUE, inserts HDU after current HDU, otherwise before
+);
+
+/** Updates the FITS file image, given the desired region and z-plane. a new
+ *  IMAGE HDU is inserted before or after, depending on the AFTER parameter,
+ *  the current HDU.
+ *
+ *  @return bool        TRUE is the write was successful, otherwise FALSE.
+ */
+bool psFitsUpdateImage(
+    psFits* fits,                      ///< the psFits object
+    const psImage* input,              ///< the image to output
+    int x0,                            ///< psImage's x-axis origin in FITS image coordinates
+    int y0,                            ///< psImage's y-axis origin in FITS image coordinates
+    int z                              ///< the z-planes of the FITS image data cube to write
+);
+
+psArray *psFitsReadImageCube(const psFits *fits, psRegion region);
+bool psFitsWriteImageCube(psFits *fits, psMetadata *header, const psArray *input, const char *extname);
+bool psFitsUpdateImageCube(psFits *fits, const psArray *input, int x0, int y0);
+
+/// @}
+#endif // #ifndef PS_FITS_H
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsTable.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsTable.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsTable.c	(revision 22290)
@@ -0,0 +1,686 @@
+/** @file  psFitsTable.c
+ *
+ *  @brief Contains Fits I/O routines
+ *
+ *  @ingroup FileIO
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.30 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-20 19:14:22 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "psFits.h"
+#include "string.h"
+#include "psError.h"
+
+#include "psImageStructManip.h"
+#include "psMemory.h"
+#include "psString.h"
+#include "psLogMsg.h"
+#include "psTrace.h"
+#include "psVector.h"
+#include "psFitsTable.h"
+#include "psFitsHeader.h"
+#include "psAbort.h"
+#include "psAssert.h"
+
+#define MAX_STRING_LENGTH 256  // maximum length string for FITS routines
+
+long psFitsTableSize(const psFits *fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+
+    int status = 0;                     // CFITSIO status
+    long numRows;                       // Number of rows
+    if (fits_get_num_rows(fits->fd, &numRows, &status)) {
+        psFitsError(status, true, "Unable to determine number of rows in table.");
+        return -1;
+    }
+
+    return numRows;
+}
+
+// Check the FITS file in preparation for reading a table
+static bool readTableCheck(const psFits *fits // FITS file
+                           )
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    if (psFitsGetExtNum(fits) == 0 && !psFitsMoveExtNum(fits, 1, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to first extension to read table.");
+        return false;
+    }
+
+
+    // check that we are positioned on a table HDU
+    int status = 0;                     // CFITSIO status
+    int hdutype;                        // Type of HDU
+    fits_get_hdu_type(fits->fd, &hdutype, &status);
+    if (psFitsError(status, true, "Could not determine the HDU type.")) {
+        return false;
+    }
+    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
+        psError(PS_ERR_IO, true, _("Current FITS HDU is not a table."));
+        return false;
+    }
+    return true;
+}
+
+
+psMetadata* psFitsReadTableRow(const psFits* fits,
+                               int row)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(row, NULL);
+
+    if (!readTableCheck(fits)) {
+        return NULL;
+    }
+
+    // get the size of the FITS table
+    long numRows;
+    int numCols;
+    int status = 0;
+    fits_get_num_rows(fits->fd, &numRows, &status);
+    fits_get_num_cols(fits->fd, &numCols, &status);
+    if (status != 0) {
+        psFitsError(status, true, "Failed to determine the size of the current HDU table.");
+        return NULL;
+    }
+
+    psTrace("psLib.fits",5,"Table size is %ix%li\n",numCols, numRows);
+    // the row parameter in the proper range?
+    if (row < 0 || row >= numRows) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                _("Specified row, %d, is not valid for current table of %ld rows."),
+                row, numRows);
+        return NULL;
+    }
+
+    psMetadata* data = psMetadataAlloc();
+
+    int hdutype;                        // Type of HDU: need to distinguish ASCII and binary tables
+    fits_get_hdu_type(fits->fd, &hdutype, &status);
+    if (psFitsError(status, true, "Could not determine the HDU type.")) {
+        return false;
+    }
+
+    int typecode;
+    long repeat;
+    long width;
+    char name[60];
+    for (int col = 1; col <= numCols; col++) {
+        // get the column name
+        if (hdutype == BINARY_TBL) {
+            fits_get_bcolparms(fits->fd, col, name,
+                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, &status);
+        } else {
+            fits_get_acolparms(fits->fd, col, name,
+                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, &status);
+        }
+        // get the column type
+        fits_get_coltype(fits->fd, col, &typecode, &repeat, &width, &status);
+        if (psFitsError(status, true, "Unable to get column type for column %d", col)) {
+            psFree(data);
+            return NULL;
+        }
+
+#define READ_TABLE_ROW_CASE(FITSTYPE, NATIVETYPE, TYPE, VECTYPE) \
+        case FITSTYPE: { \
+                if (repeat == 1) { \
+                    NATIVETYPE value; \
+                    int anynul = 0; \
+                    fits_read_col(fits->fd, FITSTYPE, col,row+1, \
+                                  1, 1, NULL, &value, &anynul, &status); \
+                    psTrace("psLib.fits",5,"Column #%i, '%s', is type %i, repeat %li, Value = %g\n", \
+                            col, name, typecode, repeat, (double)value); \
+                    psMetadataAdd(data,PS_LIST_TAIL, name, \
+                                  PS_DATA_##TYPE, \
+                                  "", (ps##TYPE)value); \
+                } else { \
+                    NATIVETYPE* value = psAlloc(sizeof(NATIVETYPE)*repeat); \
+                    psVector* vec = psVectorAlloc(repeat,PS_TYPE_##VECTYPE); \
+                    int anynul = 0; \
+                    fits_read_col(fits->fd, FITSTYPE, col,row+1, \
+                                  1, repeat, NULL, value, &anynul, &status); \
+                    for (int lcv = 0; lcv < repeat; lcv++) { \
+                        vec->data.VECTYPE[lcv] = value[lcv]; \
+                    } \
+                    psMetadataAdd(data,PS_LIST_TAIL, name, PS_DATA_VECTOR, "", vec); \
+                    psFree(value); \
+                    psFree(vec); \
+                } \
+                break; \
+            }
+
+        switch (typecode) {
+          case TBYTE:
+          case TSHORT:
+          case TLONGLONG:
+            READ_TABLE_ROW_CASE(TLONG, long, S32, S32);
+            READ_TABLE_ROW_CASE(TFLOAT, float, F32, F32);
+            READ_TABLE_ROW_CASE(TDOUBLE, double, F64, F64);
+            READ_TABLE_ROW_CASE(TLOGICAL, bool, BOOL, S8);
+          case TSTRING: {
+              psString value = psStringAlloc(repeat);
+              int anynul = 0;
+              fits_read_col(fits->fd, TSTRING, col,row+1, 1, 1, NULL, &value, &anynul, &status);
+              psTrace("psLib.fits", 5, "Column #%i, '%s', is type %i, repeat %li, value = %s\n",
+                      col, name, typecode, repeat, value);
+              if (anynul == 0) {
+                  psMetadataAdd(data,PS_LIST_TAIL, name, PS_DATA_STRING, NULL, value);
+              }
+              psFree(value);
+              break;
+          }
+          default:
+            psWarning("Data type (%d) not supportted for column %d", typecode, col);
+            psTrace("psLib.fits", 2, "Column %d or row %d was of a non primitive type, %d",
+                    col, row, typecode);
+        }
+
+        if (psFitsError(status, true, "Failed to retrieve table element (%d,%d)", col, row)) {
+            psFree(data);
+            return NULL;
+        }
+    }
+
+    return data;
+}
+
+psArray* psFitsReadTableColumn(const psFits* fits,
+                               const char* colname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(colname, NULL);
+
+    if (!readTableCheck(fits)) {
+        return NULL;
+    }
+
+    int colnum = 0;
+    int status = 0;
+
+    // find the column by name
+    if (fits_get_colnum(fits->fd, CASESEN, (char*)colname, &colnum, &status) != 0) {
+        psFitsError(status, true, "Specified column, %s, was not found.", colname);
+        return NULL;
+    }
+
+    // get the number of rows
+    long numRows = psFitsTableSize(fits);
+    if (numRows == -1) {
+        return NULL;
+    }
+
+    // get the column length.
+    int width;
+    if (fits_get_col_display_width(fits->fd, colnum, &width, &status) != 0) {
+        psFitsError(status, true, "Could not determine the datatype of the table column.");
+        return NULL;
+    }
+
+    // allocate the buffers
+    psArray* result = psArrayAlloc(numRows);
+    for (int row = 0; row < numRows; row++) {
+        result->data[row] = psStringAlloc(width);
+    }
+
+    fits_read_col_str(fits->fd, colnum, 1, 1, numRows, "", (char**)result->data, NULL, &status);
+    if (psFitsError(status, true, "Failed to read table column.")) {
+        psFree(result);
+        return NULL;
+    }
+
+    return result;
+}
+
+psVector* psFitsReadTableColumnNum(const psFits* fits,
+                                   const char* colname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(colname, NULL);
+
+    if (!readTableCheck(fits)) {
+        return NULL;
+    }
+
+    int status = 0;
+    int colnum = 0;
+
+    // find the column by name
+    if (fits_get_colnum(fits->fd, CASESEN, (char*)colname, &colnum, &status) != 0) {
+        psFitsError(status, true, "Specified column, %s, was not found.", colname);
+        return NULL;
+    }
+
+    // get the number of rows
+    long numRows = psFitsTableSize(fits);
+    if (numRows == -1) {
+        return NULL;
+    }
+
+    // get the column datatype.
+    int typecode;
+    long repeat;
+    long width;
+    if (fits_get_eqcoltype(fits->fd, colnum, &typecode, &repeat, &width, &status) != 0) {
+        psFitsError(status, true, "Could not determine the datatype of the table column.");
+        return NULL;
+    }
+
+    psVector* result = psVectorAlloc(numRows, p_psFitsTypeFromCfitsio(typecode));
+
+    fits_read_col(fits->fd, typecode, colnum, 1, 1, numRows, NULL, (psPtr)(result->data.U8), NULL, &status);
+    if (psFitsError(status, true, "Failed to read table column.")) {
+        psFree(result);
+        return NULL;
+    }
+
+    return result;
+}
+
+
+psArray* psFitsReadTable(const psFits* fits)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    if (!readTableCheck(fits)) {
+        return NULL;
+    }
+
+    // get the number of rows
+    long numRows = psFitsTableSize(fits);
+    if (numRows == -1) {
+        return NULL;
+    }
+
+    psArray* table = psArrayAlloc(numRows);
+
+    for (int row = 0; row < numRows; row++) {
+        psTrace("psLib.fits",5,"Reading row %i of %li\n", row, numRows);
+        table->data[row] = psFitsReadTableRow(fits,row);
+    }
+
+    return table;
+}
+
+bool psFitsWriteTable(psFits* fits,
+                      const psMetadata* header,
+                      const psArray* table,
+                      const char *extname)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    if (!psFitsMoveLast(fits)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to move to last extension to write table");
+        return false;
+    }
+    return psFitsInsertTable(fits, header, table, extname, true);
+}
+
+
+// Return the size of the column
+static inline size_t columnSize(const psMetadataItem *item // Item for which to get the size
+                               )
+{
+    if (PS_DATA_IS_PRIMITIVE(item->type)) {
+        return 1;
+    }
+    switch (item->type) {
+    case PS_DATA_STRING:
+        return strlen(item->data.V);
+    case PS_DATA_VECTOR: {
+            psVector *vector = item->data.V;
+            return vector->n;
+        }
+    default:
+        psAbort("Shouldn't ever get here.");
+    }
+    return 0;
+}
+
+// Get the TFORM character, given a PS type
+static inline char getTForm(psDataType type)
+{
+    switch (type) {
+    case PS_TYPE_U8:
+    case PS_TYPE_S8:
+        return 'B';
+    case PS_TYPE_S16:
+        return 'I';
+    case PS_TYPE_S32:
+        return 'J';
+    case PS_TYPE_U64:
+    case PS_TYPE_S64:
+        return 'K';
+    case PS_TYPE_U16:
+        return 'U';
+    case PS_TYPE_U32:
+        return 'V';
+    case PS_TYPE_F32:
+        return 'E';
+    case PS_TYPE_F64:
+        return 'D';
+    case PS_TYPE_BOOL:
+        return 'L';
+    case PS_DATA_STRING:
+        return 'A';
+    default:
+        psError(PS_ERR_UNKNOWN, true, "Unknown type: %x\n", type);
+        return '?';
+    }
+}
+
+
+// Column specification
+// Included here, because there's no need for the user to have access to it
+typedef struct {
+    psDataType type;                    // psLib type (e.g., PS_DATA_STRING or PS_TYPE_F32)
+    size_t size;                        // Size (number of repeats)
+    psElemType vectorType;              // psLib type of vectors
+} colSpec;
+
+
+bool psFitsInsertTable(psFits* fits,
+                       const psMetadata* header,
+                       const psArray* table,
+                       const char *extname,
+                       bool after)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_ARRAY_NON_NULL(table, false);
+
+    int status = 0;
+
+    long numRows = table->n;
+    if (numRows < 1) {
+        // no table data, what can I do?
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                _("Can't create a table without any rows."));
+        return false;
+    }
+
+    // Find the unique items in the array of metadata 'rows', and their sizes
+    psMetadata *colSpecs = psMetadataAlloc(); // Column specifications
+    size_t rowSize = 0;                 // Size (in bytes) of each row
+    for (long i = 0; i < numRows; i++) {
+        psMetadata* row = table->data[i];
+        if (!row) {
+            continue;
+        }
+        psMetadataIterator *rowIter = psMetadataIteratorAlloc(row, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *colItem;        // Column item, from iteration
+        while ((colItem = psMetadataGetAndIncrement(rowIter))) {
+            if (!(PS_DATA_IS_PRIMITIVE(colItem->type) || colItem->type == PS_DATA_STRING ||
+                    colItem->type == PS_DATA_VECTOR)) {
+                // unsupported type -- treating as an error
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                        "Unsupported data type (%d) for Metadata Item '%s' in row %ld.",
+                        colItem->type, colItem->name, i);
+                psFree(rowIter);
+                psFree(colSpecs);
+                return false;
+            }
+
+            size_t size = columnSize(colItem); // Size for this column
+
+            // Check to see if we know about this one; or update the size if required
+            psMetadataItem *colSpecItem = psMetadataLookup(colSpecs, colItem->name);
+            if (!colSpecItem) {
+                // A new one!
+                colSpec *spec = psAlloc(sizeof(colSpec)); // Specification for this column
+                spec->type = colItem->type;
+                spec->size = size;
+                if (colItem->type == PS_DATA_VECTOR) {
+                    psVector *vector = colItem->data.V; // The vector
+                    spec->vectorType = vector->type.type;
+                }
+                psMetadataAddPtr(colSpecs, PS_LIST_TAIL, colItem->name, PS_DATA_UNKNOWN, "", spec);
+                psFree(spec);           // Drop reference
+                rowSize += PSELEMTYPE_SIZEOF(spec->type);
+            } else {
+                colSpec *spec = colSpecItem->data.V; // The specification
+                if (size > spec->size) {
+                    spec->size = size;
+                }
+                if (colItem->type != spec->type) {
+                    psWarning("Differing type found for column %s: %x vs %x --- using the first found.\n",
+                              colSpecItem->name, colItem->type, spec->type);
+                }
+                if (colItem->type == PS_DATA_VECTOR) {
+                    psVector *vector = colItem->data.V; // The vector
+                    if (vector->type.type != spec->vectorType) {
+                        psWarning("Differing vector type found for column %s: %x vs %x "
+                                 "--- using the first found.\n", colSpecItem->name, vector->type.type,
+                                 spec->vectorType);
+                    }
+                }
+            }
+        }
+        psFree(rowIter);
+    }
+
+    long numColumns = colSpecs->list->n;// Number of columns
+    if (numColumns == 0) {
+        // No table columns found
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Did not find any column data to write to a table.");
+        psFree(colSpecs);
+        return false;
+    }
+
+    // Create array of column names and types.
+    psArray *columnNames = psArrayAlloc(numColumns); // Array of column names, for cfitsio
+    psArray *columnTypes = psArrayAlloc(numColumns); // Array of column types, for cfitsio
+    psMetadataIterator *colSpecsIter = psMetadataIteratorAlloc(colSpecs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *colSpecItem;        // Column specification item, from iteration
+    for (long i = 0; (colSpecItem = psMetadataGetAndIncrement(colSpecsIter)); i++) {
+        colSpec *spec = colSpecItem->data.V; // The specification
+        columnNames->data[i] = psMemIncrRefCounter(colSpecItem->name);
+        psString colType = NULL;        // The column type
+        if (spec->type == PS_DATA_VECTOR) {
+            psStringAppend(&colType, "%zd%c", spec->size, getTForm(spec->vectorType));
+        } else {
+            psStringAppend(&colType, "%zd%c", spec->size, getTForm(spec->type));
+        }
+        columnTypes->data[i] = colType;
+    }
+
+    // Create the table HDU
+    int numHDUs = psFitsGetSize(fits);  // Number of HDUs in file
+    if (numHDUs == 0) {
+        // We're creating the first extension
+        fits_create_tbl(fits->fd,
+                        BINARY_TBL,
+                        table->n, // number of rows in table
+                        numColumns, // number of columns in table
+                        (char**)columnNames->data, // names of the columns
+                        (char**)columnTypes->data, // format of the columns
+                        NULL, // physical unit of columns
+                        NULL, // skip extension name: we set the by hand below
+                        &status);
+    } else {
+        if (!after) {
+            if (psFitsGetExtNum(fits) == 0) {
+                // We're creating a replacement primary HDU.
+                // Set status to signal fits_insert_img to insert a new primary HDU
+                status = PREPEND_PRIMARY;
+            } else {
+                // Move back one to perform an insert after the previous HDU
+                psFitsMoveExtNum(fits, -1, true);
+            }
+        }
+        // Insert the table
+        fits_insert_btbl(fits->fd,
+                         table->n, // number of rows in table
+                         numColumns, // number of columns in table
+                         (char**)columnNames->data, // names of the columns
+                         (char**)columnTypes->data, // format of the columns
+                         NULL, // physical unit of columns
+                         NULL, // skip extension name: we set this by hand below
+                         0, &status);
+    }
+    psFree(columnNames);
+    psFree(columnTypes);
+
+    if (status != 0) {
+        psFitsError(status, true, "Unable to create FITS table with %ld columns and %ld rows",
+                    numColumns, table->n);
+        psFree(colSpecsIter);
+        psFree(colSpecs);
+        return false;
+    }
+
+    // Write header
+    if (header && !psFitsWriteHeader(fits, header)) {
+        psError(PS_ERR_IO, false, "Unable to write FITS header.\n");
+        psFree(colSpecsIter);
+        psFree(colSpecs);
+        return false;
+    }
+
+    // write the header, if any.
+    if (extname && strlen(extname) > 0) {
+        if (!psFitsSetExtName(fits, extname)) {
+            psError(PS_ERR_IO, false, "Unable to write FITS header extension name.\n");
+            psFree(colSpecsIter);
+            psFree(colSpecs);
+            return false;
+        }
+    }
+
+    // cfitsio requires that we write the data by columns --- urgh!
+    psMetadataIteratorSet(colSpecsIter, PS_LIST_HEAD);
+    for (long colNum = 1; (colSpecItem = psMetadataGetAndIncrement(colSpecsIter)); colNum++) {
+        // Note: colNum is unit-indexed, because it's for cfitsio
+        colSpec *spec = colSpecItem->data.V; // The specification
+        if (PS_DATA_IS_PRIMITIVE(spec->type)) {
+            size_t dataSize = PSELEMTYPE_SIZEOF(spec->type); // Size (in bytes) of this type
+            psVector *columnData = psVectorAlloc(table->n, spec->type); // The raw row data, to be written
+            psVectorInit(columnData, 0);
+            for (long i = 0; i < table->n; i++) {
+                psMetadata *row = table->data[i]; // The row of interest
+                psMetadataItem *dataItem = psMetadataLookup(row, colSpecItem->name); // The value of interest
+                memcpy(&columnData->data.U8[i * dataSize], &dataItem->data, dataSize);
+            }
+
+            int fitsDataType;           // Data type for cfitsio
+            p_psFitsTypeToCfitsio(spec->type, NULL, NULL, &fitsDataType);
+            fits_write_col(fits->fd,
+                           fitsDataType,
+                           colNum, // column number
+                           1, // first row
+                           1, // first element
+                           table->n, // number of rows
+                           columnData->data.U8, // the data
+                           &status);
+            psFree(columnData);
+        } else {
+            switch (spec->type) {
+            case PS_DATA_STRING: {
+                    psArray *strings = psArrayAlloc(table->n); // Array of strings
+                    for (long i = 0; i < table->n; i++) {
+                        psMetadata *row = table->data[i]; // The row of interest
+                        strings->data[i] = psMemIncrRefCounter(psMetadataLookupStr(NULL, row,
+                                                               colSpecItem->name));
+                    }
+                    fits_write_col_str(fits->fd, colNum, 1, 1, table->n, (char**)strings->data, &status);
+                    psFree(strings);
+                    break;
+                }
+            case PS_DATA_VECTOR: {
+                    size_t dataSize = PSELEMTYPE_SIZEOF(spec->vectorType); // Size of data, in bytes
+                    psVector *columnData = psVectorAlloc(spec->size * table->n * dataSize, PS_TYPE_U8);
+                    psVectorInit(columnData, 0);
+                    for (long i = 0; i < table->n; i++) {
+                        psMetadata *row = table->data[i]; // The row of interest
+                        psMetadataItem* dataItem = psMetadataLookup(row, colSpecItem->name);
+                        if (dataItem->type != PS_DATA_VECTOR) {
+                            // Just in case --- get a zero instead of some weird result
+                            continue;
+                        }
+                        psVector *vector = dataItem->data.V;
+                        memcpy(&columnData->data.U8[i * dataSize * spec->size], vector->data.U8,
+                               vector->n * dataSize);
+                    }
+
+                    int fitsDataType;           // Data type for cfitsio
+                    p_psFitsTypeToCfitsio(spec->vectorType, NULL, NULL, &fitsDataType);
+                    fits_write_col(fits->fd, fitsDataType, colNum, 1, 1, table->n * spec->size,
+                                   columnData->data.U8, &status);
+                    psFree(columnData);
+                    break;
+                }
+            default:
+                psAbort("Should never get here.\n");
+            }
+        }
+
+        // Check error status from writing column
+        if (status != 0) {
+            psFitsError(status, true, "Unable to write column %ld of FITS table", colNum);
+            psFree(colSpecsIter);
+            psFree(colSpecs);
+            return false;
+        }
+    }
+
+    psFree(colSpecsIter);
+    psFree(colSpecs);
+
+    return true;
+}
+
+bool psFitsUpdateTable(psFits* fits,
+                       const psMetadata* data,
+                       int row)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_METADATA_NON_NULL(data, false);
+    PS_ASSERT_INT_NONNEGATIVE(row, false);
+
+    if (!readTableCheck(fits)) {
+        return NULL;
+    }
+
+    int status = 0;
+    psMetadataIterator* iter = psMetadataIteratorAlloc((psPtr)data, PS_LIST_HEAD, NULL);
+    psMetadataItem* item;
+    while ( (item=psMetadataGetAndIncrement(iter)) != NULL) {
+        if (PS_DATA_IS_PRIMITIVE(item->type) ||
+                item->type == PS_DATA_BOOL ||
+                item->type == PS_DATA_STRING) {
+            // operating on primitive data type or string, i.e., not a complex object
+            int colnum = 0;
+
+            if (fits_get_colnum(fits->fd, CASESEN, item->name, &colnum, &status) == 0) {
+                // cooresponding column found in table
+                int dataType;
+                p_psFitsTypeToCfitsio(item->type, NULL, NULL, &dataType);
+
+                if (fits_write_col(fits->fd, dataType, colnum, row+1, 1, 1, &item->data, &status) != 0) {
+                    psFitsError(status, true, "Could not write data to file.");
+                    psFree(iter);
+                    return false;
+                }
+            } else {
+                // the column was not found.
+                psWarning("No column with the name '%s' exists in the table.", item->name);
+            }
+        }
+    }
+
+    psFree(iter);
+
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsTable.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsTable.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psLib/src/fits/psFitsTable.h	(revision 22290)
@@ -0,0 +1,122 @@
+/* @file  psFitsTable.h
+ * @brief Contains Fits I/O routines
+ *
+ * @author EAM, PAP, JH
+ * @author Robert DeSonia, MHPCC
+ *
+ * @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-10-09 02:56:23 $
+ * Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PS_FITSTABLE_H
+#define PS_FITSTABLE_H
+
+/// @addtogroup FileIO Input/Output
+/// @{
+
+#include "psFits.h"
+
+#include "psType.h"
+#include "psArray.h"
+#include "psVector.h"
+#include "psMetadata.h"
+#include "psImage.h"
+
+/// Return the number of rows in the current table
+///
+/// The current HDU type must be either PS_FITS_TYPE_BINARY_TABLE or PS_FITS_TYPE_ASCII_TABLE.
+long psFitsTableSize(const psFits *fits ///< FITS file
+                     );
+
+/** Reads a table row.  The current HDU type must be either
+ *  PS_FITS_TYPE_BINARY_TABLE or PS_FITS_TYPE_ASCII_TABLE.
+ *
+ *  @return psMetadata*    The table row's data.  The keys are the column names.
+ */
+psMetadata* psFitsReadTableRow(
+    const psFits* fits,                ///< the psFits object
+    int row                            ///< row number to read
+);
+
+/** Reads a table column.  The current HDU type must be either
+ *  PS_FITS_TYPE_BINARY_TABLE or PS_FITS_TYPE_ASCII_TABLE.
+ *
+ *  @return psArray*    Array of data items for the specified column or NULL
+ *                      if an error occurred.
+ */
+psArray* psFitsReadTableColumn(
+    const psFits* fits,                ///< the psFits object
+    const char* colname                ///< the column name
+);
+
+/** Reads a table column of numbers.  The current HDU type must be either
+ *  PS_FITS_TYPE_BINARY_TABLE or PS_FITS_TYPE_ASCII_TABLE.
+ *
+ *  @return psVector*    Vector of data for the specified column or NULL
+ *                       if an error occurred.
+ */
+psVector* psFitsReadTableColumnNum(
+    const psFits* fits,                ///< the psFits object
+    const char* colname                ///< the column name
+);
+
+
+/** Reads a whole FITS table.  The current HDU type must be either
+ *  PS_FITS_TYPE_BINARY_TABLE or PS_FITS_TYPE_ASCII_TABLE.
+ *
+ *  @return psArray*     Array of psMetadata items, which contains the output
+ *                       data items of each row.
+ *
+ *  @see psFitsReadTableRow
+ */
+psArray* psFitsReadTable(
+    const psFits* fits                  ///< the psFits object
+);
+
+/** Writes a whole FITS table. A new HDU of the type BINTABLE is appended
+ *  to the file.
+ *
+ *  @return bool        TRUE if the write was successful, otherwise FALSE
+ *
+ *  @see psFitsReadTable
+ *  @see psFitsInsertTable
+ */
+bool psFitsWriteTable(
+    psFits* fits,                      ///< the psFits object
+    const psMetadata* header,          ///< header items for the new HDU.  Can be NULL.
+    const psArray* table, ///< Array of psMetadata items, which contains the output data items of each row.
+    const char *extname                 ///< Extension name
+);
+
+/** Inserts a whole FITS table. A new HDU of the type BINTABLE is inserted either
+ *  before or after, depending on the AFTER parameter, the current HDU.
+ *
+ *  @return bool        TRUE if the insert/write was successful, otherwise FALSE
+ *
+ *  @see psFitsWriteTable
+ */
+bool psFitsInsertTable(
+    psFits* fits,                  ///< the psFits object
+    const psMetadata* header,      ///< header items for the new HDU.  Can be NULL.
+    const psArray* table, ///< Array of psMetadata items, which contains the output data items of each row.
+    const char *extname,                ///< Extension name
+    bool after    ///< TRUE if insert is done after CHDU, otherwise table is inserted before CHDU
+);
+
+/** Updates a FITS table.  The current HDU type must be either
+ *  PS_FITS_TYPE_BINARY_TABLE or PS_FITS_TYPE_ASCII_TABLE.
+ *
+ *  @return bool        TRUE if the write was successful, otherwise FALSE
+ *
+ *  @see psFitsWriteTable
+ */
+bool psFitsUpdateTable(
+    psFits* fits,                      ///< the psFits object
+    const psMetadata* data,
+    ///< Array of psMetadata items, which contains the output data items of each row.
+    int row                            ///< the row number to update.
+);
+
+/// @}
+#endif // #ifndef PS_FITS_H
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/.cvsignore
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/.cvsignore	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/.cvsignore	(revision 22290)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/Makefile.am
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/Makefile.am	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/Makefile.am	(revision 22290)
@@ -0,0 +1,59 @@
+noinst_LTLIBRARIES = libpsmodulescamera.la
+
+libpsmodulescamera_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulescamera_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulescamera_la_SOURCES  = \
+	pmFPA.c \
+	pmFPACalibration.c \
+	pmFPAConstruct.c \
+	pmFPACopy.c \
+	pmFPAHeader.c \
+	pmFPAMaskWeight.c \
+	pmFPAMosaic.c \
+	pmFPARead.c \
+	pmFPAUtils.c \
+	pmFPAWrite.c \
+	pmHDU.c \
+	pmHDUUtils.c \
+	pmHDUGenerate.c \
+	pmFPA_JPEG.c \
+	pmFPAview.c \
+	pmFPAfile.c \
+	pmFPAfileDefine.c \
+	pmFPAfileIO.c \
+	pmFPAfileFitsIO.c \
+	pmFPAFlags.c \
+	pmFPALevel.c \
+	pmFPAExtent.c \
+	pmCellSquish.c \
+	pmReadoutStack.c \
+	pmReadoutFake.c
+
+pkginclude_HEADERS = \
+	pmFPA.h \
+	pmFPACalibration.h \
+	pmFPAConstruct.h \
+	pmFPACopy.h \
+	pmFPAHeader.h \
+	pmFPAMaskWeight.h \
+	pmFPAMosaic.h \
+	pmFPARead.h \
+	pmFPAUtils.h \
+	pmFPAWrite.h \
+	pmHDU.h \
+	pmHDUUtils.h \
+	pmHDUGenerate.h \
+	pmFPA_JPEG.h \
+	pmFPAview.h \
+	pmFPAfile.h \
+	pmFPAfileDefine.h \
+	pmFPAfileIO.h \
+	pmFPAfileFitsIO.h \
+	pmFPAFlags.h \
+	pmFPALevel.h \
+	pmFPAExtent.h \
+	pmCellSquish.h \
+	pmReadoutStack.h \
+	pmReadoutFake.h
+
+CLEANFILES = *~
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmCellSquish.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmCellSquish.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmCellSquish.c	(revision 22290)
@@ -0,0 +1,176 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmShifts.h"
+#include "pmCellSquish.h"
+
+// Comparing values to get ranges
+#define COMPARE_SMALLER(TARGET, SOURCE) if ((SOURCE) < (TARGET)) (TARGET) = (SOURCE);
+#define COMPARE_BIGGER(TARGET, SOURCE) if ((SOURCE) > (TARGET)) (TARGET) = (SOURCE);
+
+
+bool pmCellSquish(pmCell *cell, psMaskType maskVal, bool useShifts)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_ARRAY_NON_NULL(cell->readouts, false);
+    psArray *readouts = cell->readouts; // Array of readouts
+    long numReadouts = readouts->n; // Number of readouts
+
+    if (numReadouts <= 1) {
+        // We squished everything there was to squish
+        return true;
+    }
+
+    pmShifts *shifts = NULL;                   // Orthogonal transfer shifts
+    if (useShifts) {
+        bool mdok;                      // Status of MD lookup
+        shifts = psMetadataLookupPtr(&mdok, cell->analysis, PM_SHIFTS_TABLE_NAME);
+        if (!mdok || !shifts) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Squishing with shifts requested, but no shifts found.");
+            return false;
+        }
+        if (shifts->num != numReadouts) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Number of shifts (%ld) does not match number of readouts (%ld).",
+                    shifts->num, numReadouts);
+            return false;
+        }
+    }
+
+    // First pass to get the bounds, make sure everything is legit.
+    int xMin = 0, xMax = 0, yMin = 0, yMax = 0; // Bounds of the squish
+    bool valid = false;                 // Do we have a valid readout?
+    int col0 = 0, row0 = 0, numCols = 0, numRows = 0;// Window parameters for the readouts
+    int xShift = 0, yShift = 0;         // Shift due to orthogonal transfer, to be applied
+    if (useShifts && shifts->xyRelative) {
+        // Correct for final shift, to put in correct frame for the image that is read out
+        xShift = - shifts->x->data.S32[shifts->num - 1];
+        yShift = - shifts->y->data.S32[shifts->num - 1];
+    }
+    for (long i = 0; i < numReadouts; i++) {
+        // Add in the shift
+        if (useShifts) {
+            if (shifts->xyRelative) {
+                // Need to accumulate shift
+                xShift += shifts->x->data.S32[i];
+                yShift += shifts->y->data.S32[i];
+            } else {
+                // Correct for final shift, to put in correct frame for the image that is read out
+                xShift = shifts->x->data.S32[i] - shifts->x->data.S32[shifts->num - 1];
+                yShift = shifts->y->data.S32[i] - shifts->y->data.S32[shifts->num - 1];
+            }
+        }
+
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout || !readout->image) {
+            continue;
+        }
+
+        if (!valid) {
+            valid = true;
+
+            col0 = readout->col0;
+            row0 = readout->row0;
+            numCols = readout->image->numCols;
+            numRows = readout->image->numRows;
+
+            if (useShifts) {
+                xMin = col0;
+                xMax = col0 + numCols;
+                yMin = row0;
+                yMax = row0 + numRows;
+            }
+        } else {
+            if (readout->col0 != col0 || readout->row0 != row0 ||
+                readout->image->numCols != numCols || readout->image->numRows != numRows) {
+                // Everything should have the same window because we've read it in from an image cube
+                psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                        "Readout window [%d:%d,%d:%d] doesn't match canonical window [%d:%d,%d:%d]",
+                        readout->col0, readout->col0 + readout->image->numCols,
+                        readout->row0, readout->row0 + readout->image->numRows,
+                        col0, col0 + numCols, row0, row0 + numRows);
+                return false;
+            }
+
+            if (useShifts) {
+                // If there is shifting, the actual window may change
+                int xMinTest = readout->col0 + xShift; // Minimum x value
+                int xMaxTest = readout->col0 + readout->image->numCols + xShift; // Maximum x value
+                int yMinTest = readout->row0 + yShift; // Minimum y value
+                int yMaxTest = readout->row0 + readout->image->numRows + yShift; // Maximum y value
+                COMPARE_SMALLER(xMin, xMinTest);
+                COMPARE_BIGGER(xMax, xMaxTest);
+                COMPARE_SMALLER(yMin, yMinTest);
+                COMPARE_BIGGER(yMax, yMaxTest);
+            }
+        }
+    }
+
+    if (useShifts) {
+        // Size of combined image, after shifts applied
+        numCols = xMax - xMin + 1;
+        numRows = yMax - yMin + 1;
+    }
+    psImage *squishImage = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Squished image
+    psImageInit(squishImage, 0.0);
+    psImage *squishMask = psImageAlloc(numCols, numRows, PS_TYPE_MASK); // Squished mask
+    psImageInit(squishMask, 0);
+
+    // Second pass to do the squish
+    xShift = yShift = 0;                // Reset the accumulated shifts
+    if (useShifts && shifts->xyRelative) {
+        // Correct for final shift, to put in correct frame for the image that is read out
+        xShift = - shifts->x->data.S32[shifts->num - 1];
+        yShift = - shifts->y->data.S32[shifts->num - 1];
+    }
+    for (long i = 0; i < numReadouts; i++) {
+        if (useShifts) {
+            if (shifts->xyRelative) {
+                // Need to accumulate shift
+                xShift += shifts->x->data.S32[i];
+                yShift += shifts->y->data.S32[i];
+            } else {
+                // Correct for final shift, to put in correct frame for the image that is read out
+                xShift = shifts->x->data.S32[i] - shifts->x->data.S32[shifts->num - 1];
+                yShift = shifts->y->data.S32[i] - shifts->y->data.S32[shifts->num - 1];
+            }
+        }
+
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout || !readout->image) {
+            continue;
+        }
+
+        int xOffset = xMin - readout->col0; // Offset to squished readout in x
+        int yOffset = yMin - readout->row0; // Offset to squished readout in y
+        if (useShifts) {
+            xOffset += xShift;
+            yOffset += yShift;
+        }
+
+        psImage *image = readout->image; // The image of interest
+        psImage *mask = readout->mask; // The mask of interest
+        for (int y = 0; y < readout->image->numRows; y++) {
+            int ySquish = y + yOffset; // Position on squished readout in y
+            for (int x = 0; x < readout->image->numCols; x++) {
+                int xSquish = x + xOffset; // Position on squished readout in x
+                squishImage->data.F32[ySquish][xSquish] += image->data.F32[y][x];
+                if (mask) {
+                    squishMask->data.PS_TYPE_MASK_DATA[ySquish][xSquish] |= mask->data.U8[y][x] & maskVal;
+                }
+            }
+        }
+    }
+
+    pmCellFreeReadouts(cell);
+    pmReadout *squishRO = pmReadoutAlloc(cell); // New readout to hold squished image
+    squishRO->image = squishImage;
+    squishRO->mask = squishMask;
+    squishRO->row0 = yMin;
+    squishRO->col0 = xMin;
+    psFree(squishRO);               // Drop reference
+
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmCellSquish.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmCellSquish.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmCellSquish.h	(revision 22290)
@@ -0,0 +1,13 @@
+#ifndef PM_CELL_SQUISH_H
+#define PM_CELL_SQUISH_H
+
+/// Squish (combine all component readouts of) a cell
+///
+/// The component readouts are combined, optionally taking into account orthogonal transfer shifts (assumed to
+/// already have been read) and masks.
+bool pmCellSquish(pmCell *cell,         ///< Cell to have readouts combined
+                  psMaskType maskVal,   ///< Value to be masked
+                  bool useShifts        ///< Use the shifts when squishing?
+    );
+
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA.c	(revision 22290)
@@ -0,0 +1,446 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <strings.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmConcepts.h"
+#include "pmMaskBadPixels.h"
+
+static void readoutFree(pmReadout *readout)
+{
+    // if this readout has a parent, drop that instance
+    if (readout->parent) {
+        psTrace("psModules.camera", 9, "Removing readout %zd from cell %zd...\n",
+                (size_t)readout, (size_t)readout->parent);
+        psArray *readouts = readout->parent->readouts;
+        for (int i = 0; i < readouts->n; i++) {
+            if (readouts->data[i] == readout) {
+                readouts->data[i] = NULL;
+            }
+        }
+    }
+    psTrace("psModules.camera", 9, "Freeing readout %zd\n", (size_t) readout);
+
+    psFree(readout->image);
+    psFree(readout->mask);
+    psFree(readout->weight);
+    psFree(readout->analysis);
+    psFree(readout->bias);
+}
+
+static void cellFree(pmCell *cell)
+{
+
+    // if this cell has a parent, drop that instance
+    if (cell->parent) {
+        psTrace("psModules.camera", 9, "Removing cell %zd from chip %zd...\n",
+                (size_t)cell, (size_t)cell->parent);
+        psArray *cells = cell->parent->cells;
+        for (int i = 0; i < cells->n; i++) {
+            if (cells->data[i] == cell) {
+                cells->data[i] = NULL;
+            }
+        }
+    }
+    psTrace("psModules.camera", 9, "Freeing cell %zd\n", (size_t)cell);
+    pmCellFreeReadouts(cell);
+    psFree(cell->readouts);
+
+    psFree(cell->concepts);
+    psFree(cell->analysis);
+    psFree(cell->config);
+    psFree(cell->hdu);
+}
+
+static void chipFree(pmChip* chip)
+{
+    // if this chip has a parent, drop that instance
+    if (chip->parent) {
+        psTrace("psModules.camera", 9, "Removing chip %zd from fpa %zd...\n",
+                (size_t)chip, (size_t)chip->parent);
+        psArray *chips = chip->parent->chips;
+        for (int i = 0; i < chips->n; i++) {
+            if (chips->data[i] == chip) {
+                chips->data[i] = NULL;
+            }
+        }
+    }
+
+    psTrace("psModules.camera", 9, "Freeing chip %zd\n", (size_t)chip);
+    pmChipFreeCells(chip);
+    psFree(chip->cells);
+
+    psFree(chip->concepts);
+    psFree(chip->analysis);
+    psFree(chip->hdu);
+
+    # if FPA_ASTROM
+
+    psFree(chip->toFPA);
+    psFree(chip->fromFPA);
+    # endif
+}
+
+
+static void FPAFree(pmFPA *fpa)
+{
+    psTrace("psModules.camera", 9, "Freeing fpa %zd\n", (size_t)fpa);
+
+    // NULL the parent pointers
+    psArray *chips = fpa->chips;
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = chips->data[i];
+        if (! tmpChip) {
+            continue;
+        }
+        tmpChip->parent = NULL;
+    }
+    psFree(fpa->chips);
+    psFree(fpa->concepts);
+    psFree(fpa->analysis);
+    psFree((void *)fpa->camera);
+    psFree(fpa->hdu);
+
+    # if FPA_ASTROM
+
+    psFree(fpa->fromTPA);
+    psFree(fpa->toTPA);
+    psFree(fpa->toSky);
+    # endif
+}
+
+void pmCellFreeReadouts(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell,);
+
+    //
+    // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
+    // in order to avoid memory reference counter problems.
+    //
+    psArray *readouts = cell->readouts;
+    for (psS32 i = 0 ; i < readouts->n ; i++) {
+        pmReadout *tmpReadout = readouts->data[i];
+        if (! tmpReadout) {
+            continue;
+        }
+        tmpReadout->parent = NULL;
+        psTrace("psModules.camera", 9, "Will now free readout %zd...\n", (size_t)tmpReadout);
+    }
+    cell->readouts = psArrayRealloc(cell->readouts, 0);
+    cell->readouts->n = 0;
+}
+
+
+void pmChipFreeCells(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip,);
+
+    //
+    // Set the parent to NULL in all chip->cells before psFree(chip->cells)
+    // in order to avoid memory reference counter problems.
+    //
+    psArray *cells = chip->cells;
+    for (int i = 0 ; i < cells->n ; i++) {
+        pmCell *tmpCell = cells->data[i];
+        if (! tmpCell) {
+            continue;
+        }
+        tmpCell->parent = NULL;
+        pmCellFreeReadouts(tmpCell);// Drop all readouts the cell holds
+    }
+    chip->cells = psArrayRealloc(chip->cells, 0);
+    chip->cells->n = 0;
+}
+
+void pmReadoutFreeData (pmReadout *readout)
+{
+    if (!readout) {
+        return;
+    }
+
+    psFree(readout->image);
+    psFree(readout->mask);
+    psFree(readout->weight);
+    psFree(readout->bias);
+
+    psTrace("psModules.camera", 3, "Freeing readout data for %zd\n", (size_t) readout);
+
+    readout->image = NULL;
+    readout->weight = NULL;
+    readout->mask = NULL;
+    readout->bias = psListAlloc(NULL);
+}
+
+void pmCellFreeData(pmCell *cell)
+{
+    if (!cell) {
+        return;
+    }
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadoutFreeData(cell->readouts->data[i]);
+    }
+    if (cell->hdu) {
+        psFree(cell->hdu->images);
+        psFree(cell->hdu->weights);
+        psFree(cell->hdu->masks);
+        // psFree(cell->hdu->header);
+
+        cell->hdu->images = NULL;
+        cell->hdu->weights = NULL;
+        cell->hdu->masks = NULL;
+        // cell->hdu->header = NULL;
+    }
+}
+
+void pmChipFreeData(pmChip *chip)
+{
+    if (!chip) {
+        return;
+    }
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCellFreeData(chip->cells->data[i]);
+    }
+    if (chip->hdu) {
+        psFree(chip->hdu->images);
+        psFree(chip->hdu->weights);
+        psFree(chip->hdu->masks);
+        // psFree(chip->hdu->header);
+
+        chip->hdu->images = NULL;
+        chip->hdu->weights = NULL;
+        chip->hdu->masks = NULL;
+        // chip->hdu->header = NULL;
+    }
+}
+
+void pmFPAFreeData(pmFPA *fpa)
+{
+    if (!fpa) {
+        return;
+    }
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChipFreeData(fpa->chips->data[i]);
+    }
+    if (fpa->hdu) {
+        psFree(fpa->hdu->images);
+        psFree(fpa->hdu->weights);
+        psFree(fpa->hdu->masks);
+        // psFree(fpa->hdu->header);
+
+        fpa->hdu->images = NULL;
+        fpa->hdu->weights = NULL;
+        fpa->hdu->masks = NULL;
+        // fpa->hdu->header = NULL;
+    }
+}
+
+pmReadout *pmReadoutAlloc(pmCell *cell)
+{
+    pmReadout *tmpReadout = (pmReadout *)psAlloc(sizeof(pmReadout));
+    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
+
+    tmpReadout->image = NULL;
+    tmpReadout->mask = NULL;
+    tmpReadout->weight = NULL;
+    tmpReadout->bias = psListAlloc(NULL);
+    tmpReadout->analysis = psMetadataAlloc();
+    tmpReadout->parent = cell;
+    if (cell) {
+        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
+    }
+
+    tmpReadout->process = true;            // All cells are processed by default
+    tmpReadout->file_exists = false;       // file not yet identified
+    tmpReadout->data_exists = false;       // data yet read in
+
+    tmpReadout->row0 = 0;
+    tmpReadout->col0 = 0;
+
+    return(tmpReadout);
+}
+
+bool psMemCheckReadout(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) readoutFree);
+}
+
+
+pmCell *pmCellAlloc(
+    pmChip *chip,
+    const char *name)
+{
+    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
+    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
+
+    tmpCell->config = NULL;
+    tmpCell->analysis = psMetadataAlloc();
+    tmpCell->readouts = psArrayAlloc(0);
+    tmpCell->parent = chip;
+    if (chip) {
+        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
+    }
+    tmpCell->hdu = NULL;
+    tmpCell->process = true;            // All cells are processed by default
+    tmpCell->file_exists = false;       // Not yet identified
+    tmpCell->data_exists = false;       // Not yet read in
+
+    tmpCell->concepts = psMetadataAlloc();
+    tmpCell->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    if (!psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name)) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
+    }
+    pmConceptsBlankCell(tmpCell);
+
+    return tmpCell;
+}
+
+bool psMemCheckCell(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) cellFree);
+}
+
+
+pmChip *pmChipAlloc(
+    pmFPA *fpa,
+    const char *name)
+{
+    pmChip *tmpChip = (pmChip*)psAlloc(sizeof(pmChip));
+    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
+
+    #if FPA_ASTROM
+
+    tmpChip->toFPA = NULL;
+    tmpChip->fromFPA = NULL;
+    #endif
+
+    tmpChip->analysis = psMetadataAlloc();
+    tmpChip->cells = psArrayAlloc(0);
+    tmpChip->parent = fpa;
+    if (fpa) {
+        psArrayAdd(fpa->chips, 1, (psPtr)tmpChip);
+    }
+    tmpChip->hdu = NULL;
+    tmpChip->process = true;            // Work on all chips, by default
+    tmpChip->file_exists = false;       // Not yet identified
+    tmpChip->data_exists = false;       // Not yet read in
+
+    tmpChip->concepts = psMetadataAlloc();
+    tmpChip->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    if (!psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name)) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME %s to concepts.\n", name);
+    }
+    pmConceptsBlankChip(tmpChip);
+    return tmpChip;
+}
+
+bool psMemCheckChip(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) chipFree);
+}
+
+pmFPA *pmFPAAlloc(const psMetadata *camera)
+{
+    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));
+    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
+
+    #if FPA_ASTROM
+
+    tmpFPA->fromTPA = NULL;
+    tmpFPA->toTPA = NULL;
+    tmpFPA->toSky = NULL;
+    #endif
+
+    tmpFPA->analysis = psMetadataAlloc();
+    tmpFPA->camera = psMemIncrRefCounter((psPtr) camera);
+    tmpFPA->chips = psArrayAlloc(0);
+    tmpFPA->hdu = NULL;
+
+    tmpFPA->concepts = psMetadataAlloc();
+    tmpFPA->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    pmConceptsBlankFPA(tmpFPA);
+
+    return tmpFPA;
+}
+
+bool psMemCheckFPA(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) FPAFree);
+}
+
+
+// Check a cell to ensure that all component readouts have the parent pointer set correctly
+static bool cellCheckParents(pmCell *cell // Cell to check
+                            )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, true);
+
+    bool flag = true;
+    for (long i = 0; i < cell->readouts->n ; i++) {
+        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
+        if (!tmpReadout) {
+            continue;
+        }
+        if (tmpReadout->parent != cell) {
+            tmpReadout->parent = cell;
+            flag = false;
+        }
+    }
+    return flag;
+}
+
+// Check a chip to ensure that all component cells have the parent pointer set correctly
+static bool chipCheckParents(pmChip *chip // Chip to check
+                            )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, true);
+
+    bool flag = true;
+    for (long i = 0; i < chip->cells->n ; i++) {
+        pmCell *tmpCell = (pmCell*)chip->cells->data[i];
+        if (!tmpCell) {
+            continue;
+        }
+        if (tmpCell->parent != chip) {
+            tmpCell->parent = chip;
+            flag = false;
+        }
+
+        flag &= cellCheckParents(tmpCell);
+    }
+    return flag;
+}
+
+psBool pmFPACheckParents(pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    bool flag = true;
+    for (long i = 0; i < fpa->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip*)fpa->chips->data[i];
+        if (!tmpChip) {
+            continue;
+        }
+        if (tmpChip->parent != fpa) {
+            tmpChip->parent = fpa;
+            flag = false;
+        }
+
+        flag &= chipCheckParents(tmpChip);
+    }
+    return flag;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA.h	(revision 22290)
@@ -0,0 +1,194 @@
+/* @file pmFPA.h
+ * @brief Defines the focal plane hierarchy, along with functions for interacting with it
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.19 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-01-02 20:32:25 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_H
+#define PM_FPA_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+#define FPA_ASTROM 1                    ///< Include astrometry information in the structures?
+
+// Return chip position, given FPA position; calculations are all done in pixel units
+#define PM_FPA_TO_CHIP(pos, chip0, chipParity) \
+    (((pos) - (chip0))*(chipParity))
+
+// Return cell position, given chip position; calculations are all done in pixel units
+#define PM_CHIP_TO_CELL(pos, cell0, cellParity, binning) \
+    (((pos) - (cell0))*(cellParity)/(binning))
+
+// Return chip position, given a cell position; calculations are all done in pixel units
+#define PM_CELL_TO_CHIP(pos, cell0, cellParity, binning) \
+    ((pos)*(binning)*(cellParity) + (cell0))
+
+// Return FPA position, given a chip position; calculations are all done in pixel units
+#define PM_CHIP_TO_FPA(pos, chip0, chipParity) \
+    ((pos)*(chipParity) + (chip0))
+
+/// Focal plane array (the entirety of the camera)
+///
+/// The FPA is the top-level camera structure, and consists of one or more chips.  It also contains the
+/// concepts metadata appropriate to this level, a summary of analysis tasks that have been performed, the
+/// camera configuration information, any HDU that corresponds to this level for the file of interest, and
+/// astrometric transformations.  The astrometric transformations encode how to transform from the tangent
+/// plane to the sky, and back.
+typedef struct
+{
+    #if FPA_ASTROM
+    // Astrometric transformations
+    psPlaneTransform *fromTPA;  ///< Transformation from tangent plane to focal plane, or NULL
+    psPlaneTransform *toTPA;  ///< Transformation from focal plane to tangent plane, or NULL
+    psProjection *toSky;         ///< Projection from tangent plane to sky, or NULL
+    #endif
+    // Information
+    psMetadata *concepts;               ///< FPA-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *analysis;               ///< FPA-level analysis metadata
+    const psMetadata *camera;           ///< Camera configuration
+    psArray *chips;                     ///< The component chips
+    pmHDU *hdu;                         ///< FITS header data unit of interest, or NULL
+}
+pmFPA;
+
+/// A chip (contiguous detector element)
+///
+/// The chip is the mid-level camera structure, being part of an FPA, and consisting of one or more cells
+/// (e.g., a CCD).  It also contains the concepts metadata appropriate to this level, a summary of analysis
+/// tasks that have been performed, status flags, any HDU that corresponds to this level for the file of
+/// interest, and astrometric transformations.  The astrometric transformations provide transforms between the
+/// chip and FPA coordinates and back.
+typedef struct
+{
+    #if FPA_ASTROM
+    // Astrometric transformations
+    psPlaneTransform *toFPA;            ///< Transformation from chip to FPA coordinates, or NULL
+    psPlaneTransform *fromFPA;          ///< Transformation from FPA to chip coordinates, or NULL
+    #endif
+    // Information
+    psMetadata *concepts;               ///< Chip-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *analysis;               ///< Chip-level analysis metadata
+    psArray *cells;                     ///< The component cells
+    pmFPA *parent;                      ///< Parent FPA
+    bool process;                       ///< Do we bother about reading and working with this chip?
+    bool file_exists;                   ///< Does the file for this chip exist (read case only)?
+    bool data_exists;                   ///< Does the data for this chip exist (read case only)?
+    pmHDU *hdu;                         ///< FITS header data unit of interest,
+}
+pmChip;
+
+/// A cell (smallest logical unit)
+///
+/// A cell is the lowest-level camera structure, being part of a chip (e.g., an amplifier).  It may consist of
+/// one or more readouts, which are individual reads of the cell.  It also contains the concepts metadata
+/// appropriate to this level, the cell configuration information (for convenience) from the camera
+/// configuration, a summary of analysis tasks that have been performed, status flags, and any HDU that
+/// corresponds to this level for the file of interest
+
+/** Cell data structure
+ *
+ *  A cell consists of one or more readouts.  It also contains a pointer to the
+ *  cell's metadata, and its parent chip.  On the astrometry side, it also
+ *  contains coordinate transforms from the cell to chip, from the cell to
+ *  focal-plane, as well as a "quick and dirty" tranform from the cell to
+ *  sky coordinates.
+ *
+ */
+typedef struct
+{
+    psMetadata *concepts;               ///< Cell-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *config;                 ///< Cell configuration information (from CELLS in the camera config)
+    psMetadata *analysis;               ///< Cell-level analysis metadata
+    psArray *readouts;                  ///< The component readouts
+    pmChip *parent;                     ///< Parent chip
+    bool process;                       ///< Do we bother about reading and working with this cell?
+    bool file_exists;                   ///< Does the file for this cell exist (read case only)?
+    bool data_exists;                   ///< Does the data for this cell exist (read case only)?
+    pmHDU *hdu;                         ///< FITS header data unit of interest
+}
+pmCell;
+
+/// A readout (individual read of a cell)
+///
+/// A readout corresponds to an individual read of a cell (e.g., a single image as part of a video sequence,
+/// or one of multiple coadds).  It contains the actual pixels used in analysis (along with mask and weight
+/// maps).  When reading from a FITS file, the images are subimages (from CELL.TRIMSEC) of the pixels read
+/// from the appropriate HDU (at the FPA, chip or cell level).  The readout also contains a list of bias
+/// sections (prescans or overscans, or otherwise), a summary of analysis tasks that have been performed,
+/// status flags, and the offsets used for reading a FITS file incrementally.
+typedef struct
+{
+    int col0;                           ///< Column offset; non-zero if reading in columns incrementally
+    int row0;                           ///< Row offset; non-zero if reading in rows incrementally
+    psImage *image;                     ///< Imaging area of readout (corresponds to CELL.TRIMSEC region)
+    psImage *mask;                      ///< Mask of input image (corresponds to CELL.TRIMSEC region)
+    psImage *weight;                    ///< Weight of input image (corresponds to CELL.TRIMSEC region)
+    psList *bias;                       ///< List of bias (prescan/overscan) images
+    psMetadata *analysis;               ///< Readout-level analysis metadata
+    pmCell *parent;                     ///< Parent cell
+    bool process;                       ///< Do we bother about reading and working with this readout?
+    bool file_exists;                   ///< Does the file for this readout exist (read case only)?
+    bool data_exists;                   ///< Does the data for this readout exist (read case only)?
+}
+pmReadout;
+
+/// Free all readouts within a cell
+void pmCellFreeReadouts(pmCell *cell);    ///< Cell for which to free readouts
+
+/// Free all cells within a chip
+void pmChipFreeCells(pmChip *chip);       ///< Chip for which to free cells
+
+/// Free all data within a readout
+void pmReadoutFreeData(pmReadout *readout); ///< Readout for which to free data
+
+/// Free all data within a cell (all readouts as well as metadata)
+void pmCellFreeData(pmCell *cell);        ///< Cell for which to free data
+
+/// Free all data within a chip (all cells as well as metadata)
+void pmChipFreeData(pmChip *chip);        ///< Chip for which to free data
+
+/// Free all data within an FPA (all chips as well as metadata)
+void pmFPAFreeData(pmFPA *fpa);           ///< FPA for which to free data
+
+/// Allocate a readout associated with a cell
+pmReadout *pmReadoutAlloc(pmCell *cell);  ///< Parent cell, or NULL
+bool psMemCheckReadout(psPtr ptr);
+
+/// Allocate a cell associated with a chip
+///
+/// The name is used to set CELL.NAME within the concepts.
+pmCell *pmCellAlloc(pmChip *chip,       ///< Parent chip, or NULL
+                    const char *name);  ///< Name of cell, for CELL.NAME
+bool psMemCheckCell(psPtr ptr);
+
+
+/// Allocate a chip associated with an FPA
+///
+/// The name is used to set CHIP.NAME within the concepts
+pmChip *pmChipAlloc(pmFPA *fpa,         ///< Parent FPA, or NULL
+                    const char *name);  ///< Name of chip, for CHIP.NAME
+bool psMemCheckChip(psPtr ptr);
+
+
+/// Allocate an FPA
+pmFPA *pmFPAAlloc(const psMetadata *camera); ///< Camera configuration (to store in FPA)
+bool psMemCheckFPA(psPtr ptr);
+
+/// Check parent links within an FPA
+///
+/// Iterates through the FPA to verify that the "parent" links in the chip, cell and readout are set
+/// correctly.  If there are any incorrect links, they are fixed, and the function returns false.
+bool pmFPACheckParents(pmFPA *fpa);     ///< FPA to check
+
+/// @}
+#endif // #ifndef PM_FPA_H
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAAstrometry.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAAstrometry.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAAstrometry.c	(revision 22290)
@@ -0,0 +1,512 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "pmFPAAstrometry.h"
+#include "pmFPA.h"
+
+
+/*****************************************************************************
+checkValidImageCoords(): this is a private function which simply determines if
+the supplied x,y coordinates are in the range for the supplied psImage.
+ 
+XXX: What about col0 and row0
+XXX: This should return a psBool.
+XXX: Macro this for speed.
+ *****************************************************************************/
+static psS32 checkValidImageCoords(
+    double x,
+    double y,
+    psImage* tmpImage)
+{
+    PS_ASSERT_IMAGE_NON_NULL(tmpImage, 0);
+
+    // The FLT_EPSILON is because -0.0 was failing this.
+    if (((x+FLT_EPSILON) < 0.0) || (x > (double)tmpImage->numCols) ||
+            ((y+FLT_EPSILON) < 0.0) || (y > (double)tmpImage->numRows)) {
+        return (0);
+    }
+
+    return (1);
+}
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+pmCell* pmCellInFPA(
+    const psPlane* fpaCoord,
+    const pmFPA* FPA)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
+
+    pmChip* tmpChip = NULL;
+    psPlane chipCoord;
+    pmCell* outCell = NULL;
+
+    // Determine which chip contains the fpaCoords.
+    tmpChip = pmChipInFPA(fpaCoord, FPA);
+    if (tmpChip == NULL) {
+        return(NULL);
+    }
+
+    // Convert to those chip coordinates.
+    psPlane *rc = pmCoordFPAToChip(&chipCoord, fpaCoord, tmpChip);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine Chip coords.\n");
+        return(NULL);
+    }
+
+    // Determine which cell contains those chip coordinates.
+    outCell = pmCellInChip(&chipCoord, tmpChip);
+    if (outCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
+        return(NULL);
+    }
+
+    return (outCell);
+}
+
+pmChip* pmChipInFPA(
+    const psPlane* fpaCoord,
+    const pmFPA* FPA)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA->chips, NULL);
+
+    psArray* chips = FPA->chips;
+    psS32 nChips = chips->n;
+    psPlane chipCoord;
+    pmCell *tmpCell = NULL;
+
+    //
+    // Loop through every chip in this FPA.  Convert the original FPA
+    // coordinates to chip coordinates for that chip.  Then, determine if any
+    // cells in that chip contain those chip coordinates.
+    // XXX: Depending on the number of chips, and their topology, there may be
+    // a much more efficient way of doing this.
+    //
+    for (psS32 i = 0; i < nChips; i++) {
+        pmChip* tmpChip = chips->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpChip, NULL);
+        PS_ASSERT_PTR_NON_NULL(tmpChip->fromFPA, NULL);
+
+        psPlaneTransformApply(&chipCoord, tmpChip->fromFPA, fpaCoord);
+
+        tmpCell = pmCellInChip(&chipCoord, tmpChip);
+        if (tmpCell != NULL) {
+            return(tmpChip);
+        }
+    }
+
+    psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the chip.\n");
+    return (NULL);
+}
+
+
+pmCell* pmCellInChip(
+    const psPlane* chipCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psPlane cellCoord;
+    psArray* cells;
+
+    cells = chip->cells;
+    if (cells == NULL) {
+        return NULL;
+    }
+
+    //
+    // We loop over each cell in the chip.  We transform the chipCoord into
+    // a cellCoord for that cell and determine if that cellCoord is valid.
+    // If so, then we return that cell.
+    // XXX: Depending on the number of cells, and their topology, there may be
+    // a much more efficient way of doing this.
+    //
+    for (psS32 i = 0; i < cells->n; i++) {
+        pmCell* tmpCell = (pmCell* ) cells->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpCell, NULL);
+
+        psPlaneTransform *chipToCell = NULL;
+        if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
+            chipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
+            //chipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
+            chipToCell = NULL;
+        }
+        if (chipToCell == NULL) {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
+            return(NULL);
+        }
+        psArray* readouts = tmpCell->readouts;
+
+        if (readouts != NULL) {
+            for (psS32 j = 0; j < readouts->n; j++) {
+                pmReadout* tmpReadout = readouts->data[j];
+                PS_ASSERT_READOUT_NON_NULL(tmpReadout, NULL);
+
+                psPlaneTransformApply(&cellCoord,
+                                      chipToCell,
+                                      chipCoord);
+
+                if (checkValidImageCoords(cellCoord.x,
+                                          cellCoord.y,
+                                          tmpReadout->image)) {
+                    psFree(chipToCell);
+                    return (tmpCell);
+                }
+            }
+        }
+        psFree(chipToCell);
+    }
+
+    //psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
+    return (NULL);
+}
+
+
+psPlane* pmCoordCellToFPA(
+    psPlane* fpaCoord,
+    const psPlane* cellCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psPlane *rc = psPlaneTransformApply(fpaCoord, cell->toFPA, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+psPlane* pmCoordChipToFPA(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psPlane *rc = psPlaneTransformApply(outCoord, chip->toFPA, inCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform chip coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+psPlane* pmCoordFPAToChip(
+    psPlane* chipCoord,
+    const psPlane* fpaCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip->fromFPA, NULL);
+
+    psPlane *rc = psPlaneTransformApply(chipCoord, chip->fromFPA, fpaCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to Chip coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordCellToChip(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psPlane *rc = psPlaneTransformApply(outCoord, cell->toChip, inCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Cell coords to Chip coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordChipToCell(
+    psPlane* cellCoord,
+    const psPlane* chipCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+
+    pmCell *tmpCell = pmCellInChip(chipCoord, cell->parent);
+    if (tmpCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the proper cell.\n");
+        return(NULL);
+    }
+
+    psPlaneTransform *tmpChipToCell = NULL;
+    PS_ASSERT_PTR_NON_NULL(tmpCell->toChip, NULL);
+    if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
+        tmpChipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
+    } else {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
+        // XXX: tmpChipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
+        tmpChipToCell = NULL;
+    }
+    if (tmpChipToCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
+        return(NULL);
+    }
+
+    psPlane *rc = psPlaneTransformApply(cellCoord, tmpChipToCell, chipCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Chip coords to Cell coords.\n");
+    }
+    psFree(tmpChipToCell);
+    return(rc);
+}
+
+psPlane* pmCoordFPAToTP(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    double color,
+    double magnitude,
+    const pmFPA* fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    psPlane *rc = psPlaneDistortApply(outCoord, fpa->toTangentPlane, inCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to tangent plane coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordTPToFPA(
+    psPlane* fpaCoord,
+    const psPlane* tpCoord,
+    double color,
+    double magnitude,
+    const pmFPA* fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa->fromTangentPlane, NULL);
+
+    psPlane *rc = psPlaneDistortApply(fpaCoord, fpa->fromTangentPlane, tpCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+/*****************************************************************************
+XXXDeproject(outSphere, coord, projection): This private routine is a wrapper
+for p_psDeproject().  The reason: p_psDeproject() and p_psProject() combined
+do not seem to produce the original coordinates when they even though they
+should.  XXXDeproject() simply negates the ->r and ->d members of the output
+psSphere if the input ->y is larger than 0.0.  I don't know why it works.
+ 
+I'm guessing the p_psProject() and p_psDeproject() functions have bugs.
+ 
+XXX: It appears that p_psProject() and p_psDeproject() have been fixed.
+Remove this.
+ *****************************************************************************/
+psSphere* XXXDeproject(
+    psSphere *outSphere,
+    const psPlane* coord,
+    const psProjection* projection)
+{
+    psSphere *rc = p_psDeproject(outSphere, coord, projection);
+
+    if (coord->y >= 0.0) {
+        rc->d = -rc->d;
+        rc->r = -rc->r;
+    }
+
+    return(rc);
+}
+
+/*****************************************************************************
+  *****************************************************************************/
+psSphere* pmCoordTPToSky(
+    psSphere* outSphere,
+    const psPlane* tpCoord,
+    const psProjection *projection)
+{
+    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(projection, NULL);
+
+    //    psSphere *rc = XXXDeproject(outSphere, tpCoord, projection);
+    psSphere *rc = p_psDeproject(outSphere, tpCoord, projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to sky coords.\n");
+    }
+    return(rc);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToTP(
+    psPlane* tpCoord,
+    const psSphere* in,
+    const psProjection *projection)
+{
+    PS_ASSERT_PTR_NON_NULL(in, NULL);
+    PS_ASSERT_PTR_NON_NULL(projection, NULL);
+
+    psPlane *rc = p_psProject(tpCoord, in, projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to tangent plane coords.\n");
+    }
+    return(rc);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psSphere* pmCoordCellToSky(
+    psSphere* skyCoord,
+    const psPlane* cellCoord,
+    double color,
+    double magnitude,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toFPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->toTangentPlane, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->projection, NULL);
+    psPlane fpaCoord;
+    psPlane tpCoord;
+    psPlane *rc;
+    pmFPA* parFPA = (cell->parent)->parent;
+
+    // Convert the input cell coordinates to FPA coordinates.
+    rc = psPlaneTransformApply(&fpaCoord, cell->toFPA, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to FPA coords.\n");
+        return(NULL);
+    }
+
+    // Convert the FPA coordinates to tangent plane Coordinates.
+    rc = psPlaneDistortApply(&tpCoord, parFPA->toTangentPlane, &fpaCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform FPA coords to tangent plane coords.\n");
+        return(NULL);
+    }
+
+    // Convert the tangent plane Coordinates to sky coordinates.
+    psSphere *rc2 = pmCoordTPToSky(skyCoord, &tpCoord, parFPA->projection);
+    if (rc2 == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to sky coords.\n");
+    }
+
+    return(rc2);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToCell(
+    psPlane* cellCoord,
+    const psSphere* skyCoord,
+    float color,
+    float magnitude,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
+    pmChip *parChip = cell->parent;
+    pmFPA *parFPA = parChip->parent;
+    psPlane tpCoord;
+    psPlane fpaCoord;
+    psPlane chipCoord;
+    psPlane *rc;
+
+    // Convert the skyCoords to tangent plane coords.
+    rc = pmCoordSkyToTP(&tpCoord, skyCoord, parFPA->projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine tangent plane coords.\n");
+        return(NULL);
+    }
+
+    // Convert the tangent plane coords to FPA coords.
+    rc = pmCoordTPToFPA(&fpaCoord, &tpCoord, color, magnitude, parFPA);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine FPA coords.\n");
+        return(NULL);
+    }
+
+    // Convert the FPA coords to chip coords.
+    rc = pmCoordFPAToChip(&chipCoord, &fpaCoord, parChip);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine chip coords.\n");
+        return(NULL);
+    }
+
+    // Convert the chip coords to cell coords.
+    rc = pmCoordChipToCell(cellCoord, &chipCoord, cell);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine cell coords.\n");
+        return(NULL);
+    }
+
+    return (cellCoord);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psSphere* pmCoordCellToSkyQuick(
+    psSphere* outSphere,
+    const psPlane* cellCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
+    psPlane outPlane;
+    psPlane *rc;
+    rc = psPlaneTransformApply(&outPlane, cell->toSky, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to sky coords.\n");
+        return(NULL);
+    }
+
+    psSphere *out = outSphere;
+    if (out == NULL) {
+        out = psSphereAlloc();
+    }
+    out->r = outPlane.y;
+    out->d = outPlane.x;
+
+    return(out);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToCellQuick(
+    psPlane* cellCoord,
+    const psSphere* skyCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
+    psPlane skyPlane;
+    skyPlane.y = skyCoord->r;
+    skyPlane.x = skyCoord->d;
+
+    psPlane *rc = psPlaneTransformApply(cellCoord, cell->toSky, &skyPlane);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to cell coords.\n");
+    }
+    return(cellCoord);
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAAstrometry.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAAstrometry.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAAstrometry.h	(revision 22290)
@@ -0,0 +1,188 @@
+#ifndef PM_FPA_ASTROMETRY_H
+#define PM_FPA_ASTROMETRY_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+/** Find cooresponding cell for given FPA coordinate
+ *
+ *  @return pmCell*    the cell cooresponding to the coord in FPA
+ */
+pmCell* pmCellInFPA(
+    const psPlane* coord,              ///< the coordinate in FPA plane
+    const pmFPA* FPA                   ///< the FPA to search for the cell
+);
+
+
+/** Find cooresponding chip for given FPA coordinate
+ *
+ *  @return pmChip*    the chip cooresponding to coord
+ */
+pmChip* pmChipInFPA(
+    const psPlane* coord,              ///< the coordinate in FPA plane
+    const pmFPA* FPA                   ///< the FPA to search for the cell
+);
+
+
+/** Find cooresponding cell for given Chip coordinate
+ *
+ *  @return pmCell*    the cell cooresponding to coord
+ */
+pmCell* pmCellInChip(
+    const psPlane* coord,              ///< the coordinate in Chip plane
+    const pmChip* chip                 ///< the chip to search for the cell
+);
+
+
+/** Translate a cell coordinate into a chip coordinate
+ *
+ *  @return psPlane*    the resulting chip coordinate
+ */
+psPlane* pmCoordCellToChip(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within Cell
+    const pmCell* cell                 ///< the Cell in interest
+);
+
+
+/** Translate a chip coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordChipToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within Chip
+    const pmChip* chip                 ///< the chip in interest
+);
+
+
+/** Translate a FPA coordinate into a Tangent Plane coordinate
+ *
+ *  @return psPlane*    the resulting Tangent Plane coordinate
+ */
+psPlane* pmCoordFPAToTP(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within FPA
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmFPA* fpa                   ///< the FPA in interest
+);
+
+
+/** Translate a Tangent Plane coordinate into a Sky coordinate
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordTPToSky(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                ///< the coordinate within Tangent Plane
+    const psProjection *projection
+);
+
+/** Translate a cell coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordCellToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a cell coordinate into a Sky coordinate
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordCellToSky(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a cell coordinate into a Sky coordinate using a 'quick and
+ *  dirty' method
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordCellToSkyQuick(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a Sky coordinate into a Tangent Plane coordinate
+ *
+ *  @return psPlane*    the resulting Tangent Plane coordinate
+ */
+psPlane* pmCoordSkyToTP(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the sky coordinate
+    const psProjection *projection
+);
+
+/** Translate a Tangent Plane coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordTPToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within tangent plane
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmFPA* fpa                   ///< the FPA of interest
+);
+
+
+/** Translate a FPA coordinate into a chip coordinate
+ *
+ *  @return psPlane*    the resulting chip coordinate
+ */
+psPlane* pmCoordFPAToChip(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the FPA coordinate
+    const pmChip* chip                 ///< the chip of interest
+);
+
+
+/** Translate a chip coordinate into a cell coordinate
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordChipToCell(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the Chip coordinate
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+/** Translate a sky coordinate into a cell coordinate
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordSkyToCell(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the Sky coordinate
+    float color,                       ///< Color of source
+    float magnitude,                   ///< Magnitude of source
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+/** Translate a sky coordinate into a cell coordinate using a 'quick and
+ *  dirty' method
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordSkyToCellQuick(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the Sky coordinate
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACalibration.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACalibration.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACalibration.c	(revision 22290)
@@ -0,0 +1,63 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmFPACalibration.h"
+
+float pmFPADarkNorm(const pmFPA *fpa, const pmFPAview *view, float expTime)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NAN);
+    PS_ASSERT_PTR_NON_NULL(view, NAN);
+
+    psMetadata *darkNorms = psMetadataLookupMetadata(NULL, fpa->camera, "DARK.NORM"); // Dark normalisations
+    if (!darkNorms) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find DARK.NORM in camera configuration.");
+        return NAN;
+    }
+
+    const char *key = psMetadataLookupStr(NULL, fpa->camera, "DARK.NORM.KEY"); // Key for normalisation
+    if (!key || strlen(key) == 0) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find DARK.NORM.KEY in camera configuration.");
+        return NAN;
+    }
+
+    psString keyResolved = pmFPANameFromRule(key, fpa, view); // Resolved version
+    if (!keyResolved || strlen(keyResolved) == 0) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to resolve DARK.NORM.KEY: %s", key);
+        return NAN;
+    }
+
+    psMetadata *polyMD = psMetadataLookupMetadata(NULL, darkNorms, keyResolved); // Metadata with polynomial
+    if (!polyMD) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find %s polynomial in the DARK.NORM metadata", keyResolved);
+        psFree(keyResolved);
+        return NAN;
+    }
+
+    psPolynomial1D *poly = psPolynomial1DfromMetadata(polyMD); // Polynomial
+    if (!poly) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine polynomial from %s in the DARK.NORM metadata",
+                keyResolved);
+        psFree(keyResolved);
+        return NAN;
+    }
+    psFree(keyResolved);
+
+    float value = psPolynomial1DEval(poly, expTime);
+
+    psFree(poly);
+
+    return value;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACalibration.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACalibration.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACalibration.h	(revision 22290)
@@ -0,0 +1,17 @@
+#ifndef PM_FPA_CALIBRATION_H
+#define PM_FPA_CALIBRATION_H
+
+/// Return the dark normalisation value
+///
+/// Unfortunately, dark current is not linear with the exposure time, but application of a polynomial
+/// correction to the exposure time should make it linear.  This function returns the appropriate value with
+/// which to normalise a dark frame.  The polynomial is obtained from DARK.NORM in the camera configuration.
+/// The specific polynomial metadata to use is provided by DARK.NORM.KEY, which is keyword expanded in the
+/// usual manner (e.g., try "{CHIP.NAME}").
+float pmFPADarkNorm(const pmFPA *fpa,   ///< FPA for which to get the normalisation
+                    const pmFPAview *view, ///< View to the FPA component of interest
+                    float expTime       ///< The nominal exposure time
+    );
+
+
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAConstruct.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAConstruct.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAConstruct.c	(revision 22290)
@@ -0,0 +1,1534 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <strings.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmConcepts.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAUtils.h"
+#include "pmHDUUtils.h"
+
+#define TABLE_OF_CONTENTS "CONTENTS"    // Name for camera format metadata containing the contents
+#define CHIP_TYPES "CHIPS"              // Name for camera format metadata containing the chip types
+#define CELL_TYPES "CELLS"              // Name for camera format metadata containing the cell types
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read data for a particular cell from the camera format description
+static psMetadata *getCellData(const psMetadata *format, // The camera format description
+                               const char *cellName // The name of the cell
+                              )
+{
+    assert(format);
+    assert(cellName && strlen(cellName) > 0);
+
+    bool status = true;                 // Result of MD lookup
+    psMetadata *cells = psMetadataLookupMetadata(&status, format, CELL_TYPES); // The CELLS
+    if (!status || !cells) {
+        psError(PS_ERR_IO, true, "Unable to find %s in camera format.\n", CELL_TYPES);
+        return NULL;
+    }
+
+    psMetadata *cellData = psMetadataLookupMetadata(&status, cells, cellName); // The data for the particular cell
+    if (!status || !cellData) {
+        psWarning("Unable to find specs for cell %s: ignored\n", cellName);
+    }
+
+    return cellData;
+}
+
+// Parse a list of first:second:third pairs in a string
+static int parseContent(psArray **first, // Array of the first values
+                         psArray **second, // Array of the second values
+                         psArray **third, // Array of the third values
+                         const char *string // The string to parse
+                        )
+{
+    assert(string && strlen(string) > 0);
+    // Must populate 'first', 'second', 'third' in order.
+    assert(!second || first);
+    assert(!third || second);
+
+    int numArrays = third ? 3 : (second ? 2 : 1); // Number of arrays
+
+    psList *values = psStringSplit(string, " ,;", true); // List of the parts
+    int num = values->n; // number of parsed content elements
+
+    // extend the arrays if they exist, create new ones if they don't
+    if (first && !*first) {
+        *first = psArrayAllocEmpty(values->n);
+    }
+    if (second && !*second) {
+        *second = psArrayAllocEmpty(values->n);
+    }
+    if (third && !*third) {
+        *third = psArrayAllocEmpty(values->n);
+    }
+
+    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
+    psString value = NULL;               // "first:second:third" string
+    while ((value = psListGetAndIncrement(valuesIter))) {
+        psArray *fst = psStringSplitArray(value, ":", true); // First, second, third
+        switch (numArrays) {
+          case 3:
+            psArrayAdd(*third, 8, fst->data[2]);
+          case 2:
+            psArrayAdd(*second, 8, fst->data[1]);
+          case 1:
+            psArrayAdd(*first, 8, fst->data[0]);
+            break;
+        default:
+          psAbort("Should never get here.");
+        }
+        psFree(fst);
+    }
+    psFree(valuesIter);
+    psFree(values);
+
+    return num;
+}
+
+// Get the name of a PHU chip or cell from the header
+static psString phuNameFromHeader(const char *name, // The name to lookup: "CELL.NAME" or "CHIP.NAME"
+                                  const psMetadata *fileInfo, // FILE within the camera format description
+                                  const psMetadata *header // Primary header
+                                 )
+{
+    assert(name && strlen(name) > 0);
+    assert(fileInfo);
+    assert(header);
+
+    bool mdok = true;                   // Result of MD lookup
+    psString keyword = psMetadataLookupStr(&mdok, fileInfo, name);
+    if (!mdok || strlen(keyword) == 0) {
+        return false;
+    }
+    psMetadataItem *resultItem = psMetadataLookup(header, keyword);
+    if (!resultItem) {
+        psError(PS_ERR_IO, true, "Unable to find %s in primary header to identify %s.\n", keyword, name);
+        return NULL;
+    }
+    return psMetadataItemParseString(resultItem);
+}
+
+// Add an HDU to the FPA
+static bool addHDUtoFPA(pmFPA *fpa,     // FPA to which to add
+                        pmHDU *hdu      // HDU to be added
+                       )
+{
+    assert(fpa);
+    assert(hdu);
+
+    if (fpa->hdu) {
+        // Something's already here
+        if (fpa->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since FPA already has one.\n");
+        }
+        return false;
+    }
+    fpa->hdu = psMemIncrRefCounter(hdu);
+    pmFPASetFileStatus(fpa, true);
+
+    return true;
+}
+
+// Add an HDU to the chip
+static bool addHDUtoChip(pmChip *chip,  // Chip to which to add
+                         pmHDU *hdu     // HDU to be added
+                        )
+{
+    assert(chip);
+    assert(hdu);
+
+    if (chip->hdu) {
+        // Something's already here
+        if (chip->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since chip already has one.\n");
+        }
+        return false;
+    }
+    chip->hdu = psMemIncrRefCounter(hdu);
+    pmChipSetFileStatus(chip, true);
+
+    return true;
+}
+
+// Add an HDU to the cell
+static bool addHDUtoCell(pmCell *cell,  // Cell to which to add
+                         pmHDU *hdu     // HDU to be added
+                        )
+{
+    assert(cell);
+    assert(hdu);
+
+    if (cell->hdu) {
+        // Something's already here
+        if (cell->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since cell already has one.\n");
+        }
+        return false;
+    }
+    cell->hdu = psMemIncrRefCounter(hdu);
+    pmCellSetFileStatus(cell, true);
+
+    return true;
+}
+
+
+// Looks up the particular content, based on the chip and cell
+static const char *getContent(const psMetadata *fileInfo, // The FILE from the camera format configuration
+                              const psMetadata *header, // The FITS header
+                              const psMetadata *contents // The CONTENTS from the camera format configuration
+                             )
+{
+    assert(fileInfo);
+    assert(contents);
+    assert(header);
+
+    const char *contentHeader = psMetadataLookupStr(NULL, fileInfo, "CONTENT"); // Keyword to get contents
+    if (!contentHeader || strlen(contentHeader) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find CONTENT in FILE within camera format configuration.\n");
+        return NULL;
+    }
+
+    psMetadataItem *contentKey = psMetadataLookup(header, contentHeader); // Key to CONTENTS menu
+    if (!contentKey) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find %s in header to determine file content.", contentHeader);
+        return NULL;
+    }
+
+    psString contentKeyStr = psMetadataItemParseString(contentKey); // Key, as a string
+
+    psTrace("psModules.camera", 5, "Looking up %s in the CONTENTS.\n", contentKeyStr);
+    const char *content = psMetadataLookupStr(NULL, contents, contentKeyStr);
+    if (!content || strlen(content) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find %s in the CONTENTS.\n", contentKeyStr);
+        return NULL;
+    }
+
+    psFree(contentKeyStr);
+
+    return content;
+}
+
+
+// Given a list of contents, put the HDU in the correct place and plug in the cell configuration information
+static bool processContents(pmFPA *fpa,  // The FPA
+                            pmChip *chip, // The chip
+                            pmHDU *hdu,  // The HDU to be added
+                            pmFPALevel level, // The level at which to add the HDU
+                            psArray *chipNames, // The chip names
+                            psArray *cellNames, // The cell names
+                            psArray *cellTypes, // The cell types
+                            const psMetadata *format // Camera format configuration
+                            )
+{
+    assert(fpa);
+    assert(cellTypes);
+    long num = cellTypes->n;            // Number of entries to add
+    assert(chip || (chipNames && chipNames->n == num));
+    assert(cellNames && cellNames->n == num);
+    assert(format);
+
+    if (hdu && level == PM_FPA_LEVEL_FPA) {
+        if (!addHDUtoFPA(fpa, hdu)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to add HDU to FPA");
+            return false;
+        }
+    }
+    // Load fpa-related concepts
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, false, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for fpa\n");
+        return false;
+    }
+
+    for (int i = 0; i < num; i++) {
+        psString cellType = cellTypes->data[i]; // The type of the cell
+
+        // Find the chip
+        pmChip *newChip;                // Chip of interest
+        if (chip) {
+            newChip = chip;
+        } else {
+            psString chipName = chipNames->data[i]; // The name of the chip
+            int chipNum = pmFPAFindChip(fpa, chipName); // The chip we're looking for
+            if (chipNum == -1) {
+                psError(PS_ERR_LOCATION_INVALID, false,
+                        "Unable to find chip %s in fpa --- ignored.\n", chipName);
+                return false;
+            }
+            newChip = fpa->chips->data[chipNum];
+        }
+
+        // Put in the HDU
+        if (hdu && level == PM_FPA_LEVEL_CHIP) {
+            addHDUtoChip(newChip, hdu);
+        }
+        // Load chip-related concepts
+        if (!pmConceptsReadChip(newChip, PM_CONCEPT_SOURCE_DEFAULTS, false, false, NULL)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for chip\n");
+            return false;
+        }
+
+        // Find the cell
+        pmCell *newCell;                // Cell of interest
+        psString cellName = cellNames->data[i]; // The name of the cell
+        int cellNum = pmChipFindCell(newChip, cellName); // The cell we're looking for
+        if (cellNum == -1) {
+            psError(PS_ERR_LOCATION_INVALID, false, "Unable to find cell %s in chip --- ignored.\n",
+                    cellName);
+            return false;
+        }
+        newCell = newChip->cells->data[cellNum];
+
+        psMetadata *cellData = getCellData(format, cellType); // Data for this cell
+
+        if (hdu && level == PM_FPA_LEVEL_CELL) {
+            addHDUtoCell(newCell, hdu);
+        }
+
+        // Put in the cell data
+        if (newCell->config) {
+            psWarning("Overwriting cell data in chip\n");
+            psFree(newCell->config); // Make way!
+        }
+        newCell->config = psMemIncrRefCounter(cellData);
+        if (!pmConceptsReadCell(newCell, PM_CONCEPT_SOURCE_CELLS | PM_CONCEPT_SOURCE_DEFAULTS,
+                                false, NULL)) {
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to read concepts from camera and defaults for cell type %s", cellType);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+#if 0
+// Return the level at which EXTENSIONS go, from the FILE metadata within the camera format
+static pmFPALevel hduLevel(const psMetadata *format // The camera format configuration
+                          )
+{
+    assert(format);
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *file = psMetadataLookupMetadata(&mdok, format, "FILE"); // File information
+    if (!mdok || !file) {
+        psError(PS_ERR_IO, true, "Unable to find FILE information in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    const char *extType = psMetadataLookupStr(&mdok, file, "EXTENSIONS");
+    if (!mdok || !extType || strlen(extType) == 0) {
+        psError(PS_ERR_IO, true, "Unable to find EXTENSIONS in the FILE information in the camera format"
+                " configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    // Where do we stick in the HDUs?
+    pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU insertion
+    if (strcasecmp(extType, "CHIP") == 0) {
+        level = PM_FPA_LEVEL_CHIP;
+    } else if (strcasecmp(extType, "CELL") == 0) {
+        level = PM_FPA_LEVEL_CELL;
+    } else if (strcasecmp(extType, "NONE") != 0) {
+        psError(PS_ERR_IO, true, "EXTENSIONS is not CHIP or CELL or NONE.\n");
+    }
+
+    return level;
+}
+#endif
+
+// Find the chip of interest within the FPA
+static bool whichChip(int *chipNum, // Chip number, modified
+                      psString *chipType, // Type of chip, modified
+                      const pmFPA *fpa, // FPA holding chip of interest
+                      const char *content // Content consisting of chipName:chipType
+                      )
+{
+    assert(chipType);
+    assert(fpa);
+    assert(content);
+
+    psArray *chipNames = NULL;
+    psArray *chipTypes = NULL;
+    if (parseContent(&chipNames, &chipTypes, NULL, content) != 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "Unable to parse chipName:chipType in %s in camera format",
+                TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psString chipName = psMemIncrRefCounter(chipNames->data[0]); // Name of chip
+    *chipType = psMemIncrRefCounter(chipTypes->data[0]); // Type of chip
+    psFree(chipNames);
+    psFree(chipTypes);
+
+    psTrace("psModules.camera", 5, "This is chip %s\n", chipName);
+
+    // Get the chip
+    *chipNum = pmFPAFindChip(fpa, chipName); // Chip number
+    if (*chipNum == -1) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find chip %s in FPA.\n", chipName);
+        psFree(chipName);
+        return false;
+    }
+    psFree(chipName);
+
+    return true;
+}
+
+
+// Process a chip, using the cellName:cellType pair
+static bool processChip(const psMetadata *format, // Camera format
+                        const psMetadataItem *chipContents, // Contents of chip, cellName:cellType pairs (either in a string or a metadata)
+                        pmFPA *fpa, // FPA of interest
+                        pmChip *chip, // Chip of interest
+                        pmFPALevel level, // Level for HDU to go
+                        pmHDU *hdu      // HDU to add
+    )
+{
+    assert(format);
+    assert(chipContents);
+    assert(fpa);
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psArray *cellNames = NULL;      // Cell names
+    psArray *cellTypes = NULL;      // Cell types
+    
+    int nParsed = 0;
+
+    switch (chipContents->type) {
+      case PS_DATA_STRING: {
+	  nParsed = parseContent(&cellNames, &cellTypes, NULL, chipContents->data.str);
+	  break;
+      }
+      case PS_DATA_METADATA: {
+	  psMetadataIterator *iter = psMetadataIteratorAlloc(chipContents->data.md, PS_LIST_HEAD, NULL); // Iterator
+	  psMetadataItem *item;           // Item from iteration
+	  while ((item = psMetadataGetAndIncrement(iter))) {
+	      if (item->type != PS_DATA_STRING) {
+		  psWarning ("Item %s in camera format chip table is not of type STR.", item->name);
+		  continue;
+	      }
+	      nParsed += parseContent(&cellNames, &cellTypes, NULL, item->data.str);
+	  }
+	  psFree (iter);
+	  break;
+      }
+      default:
+	psWarning ("Item %s in camera format chip table is not of type STR.", chipContents->name);
+	break;
+    }
+
+    if (nParsed == 0) {
+	psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+		"Unable to parse chip contents (within %s in camera format) as cellName:cellType",
+		CHIP_TYPES);
+	psFree(cellNames);
+	psFree(cellTypes);
+	return false;
+    }
+
+    if (!processContents(fpa, chip, hdu, level, NULL, cellNames, cellTypes, format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents for chip from camera format.");
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    return true;
+}
+
+// Given a chip, find the corresponding type by searching through the contents, looking for a match to its
+// name
+psString findChipType(const pmChip *chip, // Chip of interest
+                      psMetadata *contents // Contents, from camera format
+                      )
+{
+    assert(chip);
+    assert(contents);
+
+    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+    assert(chipName);
+
+    psString chipType = NULL;           // Type of chip
+    psMetadataIterator *iter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;           // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    "Item %s within %s in camera format is not of type STR.", item->name, TABLE_OF_CONTENTS);
+            psFree(iter);
+            psFree(chipType);
+            return NULL;
+        }
+
+        psArray *chipNames = NULL;  // Chip names
+        psArray *chipTypes = NULL;  // Chip types
+        if (parseContent(&chipNames, &chipTypes, NULL, item->data.str) != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse contents (within %s in camera format) as chipName:chipType",
+                    TABLE_OF_CONTENTS);
+            psFree(chipNames);
+            psFree(chipTypes);
+            psFree(iter);
+            psFree(chipType);
+            return NULL;
+        }
+
+        if (strcmp(chipName, chipNames->data[0]) == 0) {
+            if (chipType) {
+                if (strcmp(chipType, chipTypes->data[0]) != 0) {
+                    psError(PS_ERR_UNKNOWN, true,
+                            "Multiple instances of chip %s in contents, with differing chipType "
+                            "(%s vs %s)", chipName, chipType, (char*)chipTypes->data[0]);
+                    psFree(chipNames);
+                    psFree(chipTypes);
+                    psFree(iter);
+                    psFree(chipType);
+                    return NULL;
+                }
+            } else {
+                chipType = psMemIncrRefCounter(chipTypes->data[0]);
+            }
+        }
+        psFree(chipNames);
+        psFree(chipTypes);
+    }
+    psFree(iter);
+
+    if (!chipType) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to identify chip type for chip %s", chipName);
+        return NULL;
+    }
+
+    return chipType;
+}
+
+// PHU=FPA and EXTENSIONS=CHIP:
+// TABLE_OF_CONTENTS(METADATA) has a list of extensions, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types, each with cellName:cellType
+static bool addSource_FPA_CHIP(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    // Iterate over all extensions
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(contentsIter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Type for %s (%x) in %s METADATA in camera format is not STR",
+                    item->name, item->type, TABLE_OF_CONTENTS);
+            psFree(contentsIter);
+            return false;
+        }
+
+        const char *extname = item->name; // Extension name
+        pmHDU *hdu = pmHDUAlloc(extname); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        // What's in the extension?  It's specified by chipName:chipType
+        // Assume that an extension contains only a single chip, instead of multiple chips
+        psString chipType = NULL;       // Type of chip
+        int chipNum = -1;               // Chip number
+        if (!whichChip(&chipNum, &chipType, fpa, item->data.str)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine chip from contents");
+            return false;
+        }
+        pmChip *chip = fpa->chips->data[chipNum]; // Chip of interest
+
+        const psMetadataItem *chipContents = psMetadataLookup(chips, chipType); // Contents of chip
+        psFree(chipType);
+        if (!chipContents) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip type %s in %s.",
+                    chipType, CHIP_TYPES);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        if (!processChip(format, chipContents, fpa, chip, PM_FPA_LEVEL_CHIP, hdu)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to process chip %d\n", chipNum);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+// PHU=FPA and EXTENSIONS=CELL:
+// TABLE_OF_CONTENTS(METADATA) has a list of extensions, each with a chipName:cellName:cellType.
+static bool addSource_FPA_CELL(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    // Iterate over all extensions
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(contentsIter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Type for %s (%x) in %s METADATA in camera format is not STR",
+                    item->name, item->type, TABLE_OF_CONTENTS);
+            psFree(contentsIter);
+            return false;
+        }
+
+        const char *extname = item->name; // Extension name
+        pmHDU *hdu = pmHDUAlloc(extname); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        // What's in the extension?  It's specified by (possibly multiple) chipName:cellName:cellType
+
+        psArray *chipNames = NULL;      // Chip names
+        psArray *cellNames = NULL;      // Cell names
+        psArray *cellTypes = NULL;      // Cell types
+        if (parseContent(&chipNames, &cellNames, &cellTypes, item->data.str) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse extension contents (within %s->%s in camera format) as "
+                    "chipName:cellName:cellType", TABLE_OF_CONTENTS, extname);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        if (!processContents(fpa, NULL, hdu, PM_FPA_LEVEL_CELL, chipNames, cellNames, cellTypes,
+                             format)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to set contents from camera format.");
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+// PHU=FPA and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(STR) has a list of chipName:cellName:cellType.
+static bool addSource_FPA_NONE(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psString contents = psMetadataLookupStr(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    // What's in the file?  It's specified by (possibly multiple) chipName:cellName:cellType
+
+    psArray *chipNames = NULL;          // Chip names
+    psArray *cellNames = NULL;          // Cell names
+    psArray *cellTypes = NULL;          // Cell types
+    if (parseContent(&chipNames, &cellNames, &cellTypes, contents) == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Unable to parse contents (within %s in camera format) as chipName:cellName:cellType",
+                TABLE_OF_CONTENTS);
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    if (!processContents(fpa, NULL, NULL, PM_FPA_LEVEL_NONE, chipNames, cellNames, cellTypes,
+                         format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents from camera format.");
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(chipNames);
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    return true;
+}
+
+
+// PHU=CHIP and EXTENSIONS=CELL:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types(METADATA), each with extension(STR) with cellName:cellType
+static bool addSource_CHIP_CELL(pmFPAview *view, // View for PHU, modified
+                                pmFPA *fpa, // FPA to which to add
+                                pmChip *chip, // Known chip to which to add, or NULL
+                                const psMetadata *format, // The camera format
+                                pmHDU *phdu, // The Primary HDU
+                                bool install // Install the HDUs?
+                                )
+{
+    assert(view);
+    assert(fpa);
+    assert(format);
+    assert(phdu);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+
+    psString chipType = NULL;           // Type of chip
+    if (chip) {
+        // We're given the chip (adding source from view)
+        // Need to identify the chip type, which we will do by traversing the contents
+        chipType = findChipType(chip, contents);
+    } else {
+        // We're given a header, from which to identify what chip we've got, and its type
+        const char *content = getContent(fileInfo, phdu->header, contents); // The contents of this chip
+        if (!content) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine content of file.");
+            return false;
+        }
+
+        int chipNum = -1;               // Chip number
+        if (!whichChip(&chipNum, &chipType, fpa, content)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine chip from contents");
+            return false;
+        }
+        chip = fpa->chips->data[chipNum]; // Chip of interest
+        view->chip = chipNum;
+    }
+
+    if (!install) {
+        // Everything below is about installing the HDUs
+        psFree(chipType);
+        return true;
+    }
+
+    if (!addHDUtoChip(chip, phdu)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add HDU to chip\n");
+        psFree(chipType);
+        return false;
+    }
+
+    psMetadata *chipContents = psMetadataLookupMetadata(NULL, chips, chipType); // Contents of chip
+    psFree(chipType);
+    if (!chipContents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip type %s in %s.",
+                chipType, CHIP_TYPES);
+        return false;
+    }
+
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(chipContents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *contentItem;        // Content, from iteration
+    while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
+        pmHDU *hdu = pmHDUAlloc(contentItem->name); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        if (!processChip(format, contentItem, fpa, chip, PM_FPA_LEVEL_CELL, hdu)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to process chip\n");
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU, true, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for chip.");
+        return false;
+    }
+
+    return true;
+}
+
+// PHU=CHIP and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types, each with cellName:cellType
+static bool addSource_CHIP_NONE(pmFPAview *view, // View for PHU, modified
+                                pmFPA *fpa, // FPA to which to add
+                                pmChip *chip, // Known chip to which to add, or NULL
+                                const psMetadata *format, // The camera format
+                                pmHDU *phdu, // Primary HDU
+                                bool install // Install the HDUs?
+                                )
+{
+    assert(fpa);
+    assert(format);
+    assert(phdu);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+    psString chipType = NULL;           // Type of chip
+    if (chip) {
+        // We're given the chip (adding source from view)
+        // Need to identify the chip type, which we will do by traversing the contents
+        chipType = findChipType(chip, contents);
+    } else {
+        const char *content = getContent(fileInfo, phdu->header, contents); // The chip type
+
+        int chipNum = -1;               // Chip number
+        if (!whichChip(&chipNum, &chipType, fpa, content)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine chip from contents");
+            return false;
+        }
+        chip = fpa->chips->data[chipNum]; // Chip of interest
+        view->chip = chipNum;
+    }
+
+    if (!install) {
+        // Everything below is about installing the HDU
+        psFree(chipType);
+        return true;
+    }
+
+    if (!addHDUtoChip(chip, phdu)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add HDU to chip\n");
+        psFree(chipType);
+        return false;
+    }
+
+    // What's in the chip?
+    psMetadataItem *chipContents = psMetadataLookup(chips, chipType); // Contents of the chip
+    if (!chipContents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find chip type %s in %s of camera format", chipType, CHIP_TYPES);
+        return false;
+    }
+    psFree(chipType);
+
+    if (!processChip(format, chipContents, fpa, chip, PM_FPA_LEVEL_NONE, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to process chip\n");
+        return false;
+    }
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU, true, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for chip.");
+        return false;
+    }
+
+    return true;
+}
+
+// PHU=CELL and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipName:cellName:cellType
+static bool addSource_CELL_NONE(pmFPAview *view, // View for PHU, modified
+                                pmFPA *fpa, // FPA to which to add
+                                pmCell *cell, // Known cell to which to add, or NULL
+                                const psMetadata *format, // The camera format
+                                pmHDU *phdu, // The Primary HDU
+                                bool install // Install the HDUs?
+                                )
+{
+    assert(fpa);
+    assert(format);
+    assert(phdu);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+    psArray *chipNames = NULL;          // Chip names
+    psArray *cellNames = NULL;          // Cell names
+    psArray *cellTypes = NULL;          // Cell types
+    pmChip *chip = NULL;                // Chip of interest
+    if (cell) {
+        // We're given the chip and cell (adding source from view)
+        // Need to identify the cell type, which we will do by traversing the contents
+
+        chip = cell->parent;            // The chip of interest
+        psString cellType = NULL;       // Type of cell
+
+        // The below is very similar to findChipType(), but with modifications for finding the cellType
+        const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+        assert(chipName);
+        const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+        assert(cellName);
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;           // Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (item->type != PS_DATA_STRING) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                        "Item %s within %s in camera format is not of type STR.",
+                        item->name, TABLE_OF_CONTENTS);
+                psFree(chipNames);
+                psFree(cellNames);
+                psFree(cellTypes);
+                psFree(iter);
+                psFree(cellType);
+                return false;
+            }
+
+            psArray *testChipNames = NULL; // Chip names
+            psArray *testCellNames = NULL; // Cell names
+            psArray *testCellTypes = NULL; // Cell types
+            if (parseContent(&testChipNames, &testCellTypes, &testCellTypes, item->data.str) != 1) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                        "Unable to parse contents (within %s in camera format) as chipName:cellName:cellType",
+                        TABLE_OF_CONTENTS);
+                psFree(chipNames);
+                psFree(cellNames);
+                psFree(cellTypes);
+                psFree(testChipNames);
+                psFree(testCellNames);
+                psFree(testCellTypes);
+                psFree(iter);
+                psFree(cellType);
+                return false;
+            }
+
+            if (strcmp(chipName, chipNames->data[0]) == 0 && strcmp(cellName, cellNames->data[0]) == 0) {
+                if (cellType) {
+                    if (strcmp(cellType, cellTypes->data[0]) != 0) {
+                        psError(PS_ERR_UNKNOWN, true,
+                                "Multiple instances of chip %s cell %s in contents, with differing cellType "
+                                "(%s vs %s)", chipName, cellName, cellType, (char*)cellTypes->data[0]);
+                        psFree(chipNames);
+                        psFree(cellNames);
+                        psFree(cellTypes);
+                        psFree(testChipNames);
+                        psFree(testCellNames);
+                        psFree(testCellTypes);
+                        psFree(iter);
+                        psFree(cellType);
+                        return false;
+                    }
+                } else {
+                    cellType = psMemIncrRefCounter(cellTypes->data[0]);
+                    chipNames = psMemIncrRefCounter(testChipNames);
+                    cellNames = psMemIncrRefCounter(testCellNames);
+                    cellTypes = psMemIncrRefCounter(testCellTypes);
+                }
+            }
+            psFree(testChipNames);
+            psFree(testCellNames);
+            psFree(testCellTypes);
+        }
+        psFree(iter);
+
+        if (!cellType) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to identify cell type for chip %s cell %s",
+                    chipName, cellName);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+
+        // We don't really care about the cell type here --- it's taken care of by processContents
+        psFree(cellType);
+
+    } else {
+        const char *content = getContent(fileInfo, phdu->header, contents); // Content of cell
+
+        if (parseContent(&chipNames, &cellNames, &cellTypes, content) != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse cell contents (%s) as cellName:cellType", content);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+
+        int chipNum = pmFPAFindChip(fpa, chipNames->data[0]); // Chip number
+        if (chipNum == -1) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find chip %s referred to in contents",
+                    (char*)chipNames->data[0]);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+        chip = fpa->chips->data[chipNum];
+
+        int cellNum = pmChipFindCell(chip, cellNames->data[0]); // Cell number
+        if (cellNum == -1) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find cell %s referred to in contents",
+                    (char*)cellNames->data[0]);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+        cell = chip->cells->data[cellNum];
+
+        view->chip = chipNum;
+        view->cell = cellNum;
+
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+    }
+
+    if (!install) {
+        // Everything below is about installing the HDU
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return true;
+    }
+
+    if (!processContents(fpa, NULL, phdu, PM_FPA_LEVEL_NONE, chipNames, cellNames, cellTypes, format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents for cell from camera format.");
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(chipNames);
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for cell.");
+        return false;
+    }
+
+    return true;
+}
+
+
+// This is the engine for the pmFPAAddSourceFrom{Header,View} functions.
+// It uses the camera format configuration information to determine where HDUs go in the FPA.
+// It returns a view corresponding to the PHU
+static pmFPAview *addSource(pmFPA *fpa,       // The FPA
+                            const char *fpaname, // The desired FPA name
+                            const pmFPAview *phuView, // The view corresponding to the PHU, or NULL
+                            const psMetadata *header, // The PHU header, or NULL
+                            const psMetadata *format, // Format of file
+                            bool install // Install the provided header in the location that we find?
+                           )
+{
+    assert(fpa);
+    assert(phuView || header);
+    assert(format);
+
+    bool mdok;                          // Status of MD lookup
+
+    // If FPA.NAME is already defined, new name must match it; otherwise, warn the user that something
+    // potentially bad is happening.
+    if (fpaname && install) {
+        const char *currentName = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.NAME"); // Current name
+        if (mdok && currentName && strlen(currentName) > 0 && strcmp(currentName, fpaname) != 0) {
+            psWarning("FPA.NAME for new source (%s) doesn't match FPA.NAME for current "
+                     "fpa (%s).\n", fpaname, currentName);
+        }
+        psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.NAME", PS_META_REPLACE, "Name of FPA", fpaname);
+    } else if (!psMetadataLookup(fpa->concepts, "FPA.NAME")) {
+        // Make sure there is an FPA.NAME
+        psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.NAME", 0, "Name of FPA", "UNKNOWN");
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // The file information
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return NULL;
+    }
+
+    // At what level does the PHU go?
+    const char *phuType = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // What is the PHU?
+    if (!mdok || strlen(phuType) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find PHU in the format specification.\n");
+        return NULL;
+    }
+
+    // Prepare the PHU to be placed in the camera hierarchy
+    pmHDU *phdu = pmHDUAlloc(NULL);     // The primary header data unit
+    // Casting to psPtr to avoide "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+    // pointer target type"
+    phdu->header = psMemIncrRefCounter((const psPtr)header);
+    phdu->format = psMemIncrRefCounter((const psPtr)format);
+    pmFPAview *view = pmFPAviewAlloc(0); // View, to be returned
+    if (phuView) {
+        // Copy the view, for the case where we're given a view.
+        *view = *phuView;
+    }
+
+    // And at what level do the individual extensions go?
+    const char *extType = psMetadataLookupStr(&mdok, fileInfo, "EXTENSIONS"); // What's in the extns?
+    if (!mdok || strlen(extType) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find EXTENSIONS in the format specification.\n");
+        psFree(view);
+        return NULL;
+    }
+
+    // Now, there are a few cases:
+
+    // Case    PHU     EXTENSIONS     Description
+    // ====    ===     ==========     ===========
+    // 1.      FPA     CHIP           CONTENTS(METADATA) has a list of extensions, each with chipName:chipType
+    //                                CHIPS(METADATA) has a list of chip types, each with cell:type
+    // 2.      FPA     CELL           CONTENTS(METADATA) has a list of extensions, each with chip:cell:type
+    //                                No need for CHIPS.
+    // 3.      FPA     NONE           CONTENTS(STRING) has a list of extensions, chip:cell:type
+    //                                No need for CHIPS
+    // 4.      CHIP    CELL           CONTENTS(METADATA) is a menu, each with a chipName:chipType
+    //                                CHIPS(METADATA) has a list of chip types(METADATA), containg a list of
+    //                                extensions.
+    // 5.      CHIP    NONE           CONTENTS(METADATA) is a menu, each with a chipName:chipType
+    //                                CHIPS(METADATA) has a list of chip types(STRING) with cell:type
+    // 6.      CELL    NONE           CONTENTS(METADATA) is a menu, each with a chipName:cellName:cellType.
+    //                                No need for CHIPS.
+
+
+    pmFPALevel phuLevel = pmFPALevelFromName(phuType); // Level for PHU
+    pmFPALevel extLevel = pmFPALevelFromName(extType); // Level for extensions
+
+    switch (phuLevel) {
+      case PM_FPA_LEVEL_FPA: {
+          // We don't have to work out where the PHU is --- there's only one FPA.
+          // 'view' already points to the FPA.
+          switch (extLevel) {
+            case PM_FPA_LEVEL_CHIP:
+              phdu->blankPHU = true;
+              if (install) {
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_CHIP(fpa, format) ||
+                      !pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU,
+                                         true, NULL)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_CELL:
+              if (install) {
+                  phdu->blankPHU = true;
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_CELL(fpa, format) ||
+                      !pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU,
+                                         true, NULL)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_NONE:
+              if (install) {
+                  phdu->blankPHU = false;
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_NONE(fpa, format) ||
+                      !pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU,
+                                         true, NULL)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+             return view;
+            default:
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (FPA)", extType);
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          break;
+      }
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = NULL;          // Appropriate chip, if the view is specified
+          if (phuView) {
+              chip = fpa->chips->data[phuView->chip];
+          }
+          switch (extLevel) {
+            case PM_FPA_LEVEL_CELL:
+              phdu->blankPHU = true;
+              if (!addSource_CHIP_CELL(view, fpa, chip, format, phdu, install)) {
+                  psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                  psFree(phdu);
+                  psFree(view);
+                  return NULL;
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_NONE:
+              phdu->blankPHU = false;
+              if (!addSource_CHIP_NONE(view, fpa, chip, format, phdu, install)) {
+                  psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                  psFree(phdu);
+                  psFree(view);
+                  return NULL;
+              }
+              psFree(phdu);
+              return view;
+            default:
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (CHIP)", extType);
+              return NULL;
+          }
+          break;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          if (extLevel != PM_FPA_LEVEL_NONE) {
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (CELL)", extType);
+              return NULL;
+          }
+          pmChip *chip = NULL;          // Appropriate chip, if the view is specified
+          pmCell *cell = NULL;          // Appropriate cell, if the view is specified
+          if (phuView) {
+              chip = fpa->chips->data[phuView->chip];
+              cell = chip->cells->data[phuView->cell];
+          }
+          phdu->blankPHU = false;
+          if (!addSource_CELL_NONE(view, fpa, cell, format, phdu, install)) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          psFree(phdu);
+          return view;
+          break;
+      }
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Bad PHU level: %s", phuType);
+        return NULL;
+    }
+
+    return NULL;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmFPA *pmFPAConstruct(const psMetadata *camera)
+{
+    PS_ASSERT_PTR_NON_NULL(camera, NULL);
+
+    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out
+
+    bool mdok = true;                   // Status from MD lookups
+    psMetadata *components = psMetadataLookupMetadata(&mdok, camera, "FPA"); // FPA components
+    if (!mdok || !components) {
+        psError(PS_ERR_IO, true, "Failed to lookup \"FPA\"");
+        psFree(fpa);
+        return NULL;
+    }
+    psMetadataIterator *componentsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL);
+    psMetadataItem *componentsItem = NULL; // Item from components
+    while ((componentsItem = psMetadataGetAndIncrement(componentsIter))) {
+        const char *chipName = componentsItem->name; // Name of the chip
+        if (componentsItem->type != PS_DATA_STRING) {
+            psWarning("Element %s in FPA within the camera configuration is not of "
+                     "type STR (type=%x) --- ignored.\n", chipName, componentsItem->type);
+            continue;
+        }
+
+        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+        psList *cellNames = psStringSplit(componentsItem->data.V, " ,;", true); // List of cell names
+        psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
+
+        psString cellName = NULL;       // Name of cell
+        while ((cellName = psListGetAndIncrement(cellNamesIter))) {
+            pmCell *cell = pmCellAlloc(chip, cellName); // New cell
+            psFree(cell);               // Drop reference
+        }
+        psFree(chip);                   // Drop reference
+        psFree(cellNamesIter);
+        psFree(cellNames);
+    }
+    psFree(componentsIter);
+
+    return fpa;
+}
+
+bool pmFPAAddSourceFromView(pmFPA *fpa, const char *fpaname, const pmFPAview *phuView,
+                            const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(phuView, false);
+    PS_ASSERT_PTR_NON_NULL(format, false);
+
+    pmFPAview *view = addSource(fpa, fpaname, phuView, NULL, format, true);
+    bool status = (view != NULL);
+    psFree(view);
+    return status;
+}
+
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, psMetadata *phu, const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(phu, NULL);
+    PS_ASSERT_PTR_NON_NULL(format, NULL);
+
+    bool mdok = true;                   // Status from metadata lookups
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // The file information
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return NULL;
+    }
+
+    // Check the name of the FPA
+    psString fpaname = phuNameFromHeader("FPA.NAME", fileInfo, phu); // New name for the FPA
+    if (!fpaname || strlen(fpaname) == 0) {
+        psWarning("Unable to determine FPA.NAME: check for FPA.NAME in FILE in camera format");
+    }
+
+    pmFPAview *view = addSource(fpa, fpaname, NULL, phu, format, true); // View of PHU, to return
+    psFree(fpaname);
+
+    return view;
+}
+
+
+pmFPAview *pmFPAIdentifySourceFromHeader(pmFPA *fpa, psMetadata *phu, const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(phu, NULL);
+    PS_ASSERT_PTR_NON_NULL(format, NULL);
+
+    return addSource(fpa, NULL, NULL, phu, format, false);
+}
+
+
+// Print spaces to indent
+#define INDENT(FILE, LEVEL) \
+{ \
+    for (int i = 0; i < (LEVEL); i++) { \
+        fprintf(FILE, " "); \
+    } \
+}
+
+void pmFPAPrint(FILE *fd, const pmFPA *fpa, bool header, bool concepts)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa,);
+
+    INDENT(fd, 1);
+    fprintf(fd, "FPA:\n");
+    if (fpa->hdu) {
+        pmHDUPrint(fd, fpa->hdu, 2, header);
+    }
+    if (concepts) {
+        psMetadataPrint(fd, fpa->concepts, 2);
+    }
+
+    psArray *chips = fpa->chips;        // Array of chips
+    // Iterate over the FPA
+    for (int i = 0; i < chips->n; i++) {
+        INDENT(fd, 3);
+        fprintf(fd, "Chip: %d\n", i);
+        pmChip *chip = chips->data[i]; // The chip
+        if (chip->hdu) {
+            pmHDUPrint(fd, chip->hdu, 4, header);
+        }
+        if (concepts) {
+            psMetadataPrint(fd, chip->concepts, 4);
+        }
+
+        // Iterate over the chip
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            INDENT(fd, 5);
+            fprintf(fd, "Cell: %d\n", j);
+            pmCell *cell = cells->data[j]; // The cell
+            if (cell->hdu) {
+                pmHDUPrint(fd, cell->hdu, 6, header);
+            }
+            if (concepts) {
+                psMetadataPrint(fd, cell->concepts, 6);
+            }
+
+            psArray *readouts = cell->readouts; // Array of readouts
+            for (int k = 0; k < readouts->n; k++) {
+                pmReadout *readout = readouts->data[k]; // The readout
+                INDENT(fd, 6);
+                fprintf(fd, "Readout %d:\n", k);
+                INDENT(fd, 7);
+                fprintf(fd, "col0: %d\n", readout->col0);
+                INDENT(fd, 7);
+                fprintf(fd, "row0: %d\n", readout->row0);
+                psImage *image = readout->image; // The image
+                psImage *mask = readout->mask; // The mask
+                psImage *weight = readout->weight; // The weight
+                psList *bias = readout->bias; // The list of bias images
+                if (image) {
+                    INDENT(fd, 7);
+                    fprintf(fd, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 +
+                            image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
+                            image->numRows);
+                }
+                if (bias) {
+                    psListIterator *biasIter = psListIteratorAlloc(bias, PS_LIST_HEAD, false); // Iterator
+                    psImage *biasImage = NULL; // Bias image from iteration
+                    while ((biasImage = psListGetAndIncrement(biasIter))) {
+                        INDENT(fd, 7);
+                        fprintf(fd, "Bias:  [%d:%d,%d:%d] (%dx%d)\n", biasImage->col0,
+                                biasImage->col0 + biasImage->numCols, biasImage->row0,
+                                biasImage->row0 + biasImage->numRows, biasImage->numCols, biasImage->numRows);
+                    }
+                    psFree(biasIter);
+                }
+                if (mask) {
+                    INDENT(fd, 7);
+                    fprintf(fd, "Mask: [%d:%d,%d:%d] (%dx%d)\n", mask->col0, mask->col0 +
+                            mask->numCols, mask->row0, mask->row0 + mask->numRows, mask->numCols,
+                            mask->numRows);
+                }
+                if (weight) {
+                    INDENT(fd, 7);
+                    fprintf(fd, "Weight: [%d:%d,%d:%d] (%dx%d)\n", weight->col0, weight->col0 +
+                            weight->numCols, weight->row0, weight->row0 + weight->numRows, weight->numCols,
+                            weight->numRows);
+                }
+            } // Iterating over cell
+        } // Iterating over chip
+    } // Iterating over FPA
+
+}
+
+
+pmFPALevel pmFPAPHULevel(const psMetadata *format)
+{
+    PS_ASSERT_METADATA_NON_NULL(format, PM_FPA_LEVEL_NONE);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // Contents of FILE metadata
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    const char *phu = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // PHU level
+    if (!mdok || !phu || strlen(phu) == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find PHU in FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    return pmFPALevelFromName(phu);
+}
+
+pmFPALevel pmFPAExtensionsLevel(const psMetadata *format)
+{
+    PS_ASSERT_METADATA_NON_NULL(format, PM_FPA_LEVEL_NONE);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // Contents of FILE metadata
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    const char *extensions = psMetadataLookupStr(&mdok, fileInfo, "EXTENSIONS"); // EXTENSIONS level
+    if (!mdok || !extensions || strlen(extensions) == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find EXTENSIONS in FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    return pmFPALevelFromName(extensions);
+}
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAConstruct.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAConstruct.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAConstruct.h	(revision 22290)
@@ -0,0 +1,69 @@
+/* @file pmFPAConstruct.h
+ * @brief Functions to create an FPA, and add data sources to it.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_CONSTRUCT_H
+#define PM_FPA_CONSTRUCT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Construct an FPA instance on the basis of a camera configuration
+///
+/// This is the function that creates the FPA hierarchy on the basis of the camera configuration.  The "FPA"
+/// entry in the camera configuration specifies the chips (each of type STR) with their component cells listed
+/// as the corresponding values (whitespace separated).  The FPA hierarchy is created devoid of any
+/// input/output sources (i.e., HDUs).
+pmFPA *pmFPAConstruct(const psMetadata *camera ///< The camera configuration
+                     );
+
+/// Add an (input or output) source to the focal plane hierarchy, specified by a view
+///
+/// Given an FPA, add an HDU by specifying where it goes (i.e., by an FPAview).  The camera format
+/// configuration is required in order to describe how the FPA is laid out in terms of disk files.
+bool pmFPAAddSourceFromView(pmFPA *fpa,   ///< The FPA
+                            const char *fpaname, ///< FPA.NAME for the source
+                            const pmFPAview *phuView, ///< The view, corresponding to the PHU
+                            const psMetadata *format ///< Format of file
+                           );
+
+/// Add an (input or output) source to the focal plane hierarchy, specified by a (primary) header
+///
+/// Given an FPA, add an HDU by specifying a primary header, which is used to determine the FITS file
+/// contents, and therefore the proper location for the HDU.  The camera format configuration is required in
+/// order to describe how the FPA is laid out in terms of disk files.
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, ///< The FPA
+                                    psMetadata *phu, ///< Primary header of file
+                                    const psMetadata *format ///< Format of file
+                                   );
+
+/// Identify a source in the focal plane hierarchy, specified by a (primary) header
+///
+/// This is the same as pmFPAAddSourceFromHeader, except the input is not added into the FPA hierarchy.
+/// This function serves only to identify where in the hierarchy it should go, not prepare for reading, etc.
+pmFPAview *pmFPAIdentifySourceFromHeader(pmFPA *fpa, psMetadata *phu, const psMetadata *format);
+
+/// Print a representation of the FPA, including its headers and concepts.
+///
+/// This function is intended for testing and development purposes.
+void pmFPAPrint(FILE *fd,               ///< File descriptor to which to print
+                const pmFPA *fpa,       ///< FPA to print
+                bool header,            ///< Print headers?
+                bool concepts           ///< Print concepts?
+               );
+
+/// Return the PHU level for an FPA, given the format
+pmFPALevel pmFPAPHULevel(const psMetadata *format);
+
+/// Return the Extensions level for an FPA, given the format
+pmFPALevel pmFPAExtensionsLevel(const psMetadata *format);
+
+/// @}
+
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACopy.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACopy.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACopy.c	(revision 22290)
@@ -0,0 +1,546 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <strings.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAUtils.h"
+#include "pmHDUUtils.h"
+#include "pmFPACopy.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions and macros
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Copy the value for a concept
+#define COPY_CONCEPT(TARGET, SOURCE, NAME, TYPE) { \
+    psMetadataItem *targetItem = psMetadataLookup(TARGET, NAME); \
+    psMetadataItem *sourceItem = psMetadataLookup(SOURCE, NAME); \
+    targetItem->data.TYPE = sourceItem->data.TYPE; }
+
+// Find the blank (image-less) PHU, given a cell.
+static pmHDU *findBlankPHU(const pmCell *cell // The cell for which to find the PHU
+                          )
+{
+    assert(cell);
+
+    if (cell->hdu && cell->hdu->blankPHU) {
+        return cell->hdu;
+    }
+    pmChip *chip = cell->parent;        // The parent chip
+    if (chip->hdu && chip->hdu->blankPHU) {
+        return chip->hdu;
+    }
+    pmFPA *fpa = chip->parent;  // The parent FPA
+    if (fpa->hdu && fpa->hdu->blankPHU) {
+        return fpa->hdu;
+    }
+
+    return NULL;
+}
+
+// copy one of the psImage components of the readout
+static void readoutCopyComponent (psImage **target, psImage *source, psImageBinning *binning, bool xFlip, bool yFlip, bool pixels) {
+
+    if (!source) return;
+
+    if (*target) {
+        psFree(*target);
+    }
+    if (pixels) {
+        *target = psImageFlip(NULL, source, xFlip, yFlip);
+        return;
+    }
+
+    // I have the fine image size, I know the binning factor, determine the ruff image size
+    binning->nXfine = source->numCols;
+    binning->nYfine = source->numRows;
+    psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+    *target = psImageAlloc(binning->nXruff, binning->nYruff, source->type.type);
+    return;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static engine functions --- these do all the work.  Actually, cellCopy does all the work; the others
+// merely iterate on the higher-level components.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Common engine for pmCellCopy and pmCellCopyStructure
+// Does the actual splitting/splicing that's required to copy an FPA to a different representation.
+static bool cellCopy(pmCell *target,     // The target cell
+                     const pmCell *source, // The source cell, to be copied
+                     bool pixels,        // Copy the pixels?
+                     int xBin, int yBin  // (Relative) binning factors in x and y
+                    )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0 && yBin > 0);
+
+    if (!source->data_exists) {
+        // Copied everything that exists
+        return true;
+    }
+
+    // XXX this is a programming / config error
+    if (pixels && (xBin != 1 || yBin != 1)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to copy pixels if binning is set\n");
+        return false;
+    }
+
+    // the binning structure carries the information no how to rebin the images if needed
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXbin = xBin;
+    binning->nYbin = yBin;
+
+    psArray *sourceReadouts = source->readouts; // The source readouts
+    int numReadouts = sourceReadouts->n; // Number of readouts copied
+
+    // Need to check/change CELL.XPARITY and CELL.YPARITY
+    bool mdokS = true;                   // Status of MD lookup
+    bool mdokT = true;                   // Status of MD lookup
+    bool xFlip = false;                 // Switch parity in x?
+    bool yFlip = false;                 // Switch parity in y?
+
+    // enforce the following conditions:
+    // CELL.XPARITY is required
+    // CELL.XPARITY must be +/- 1
+    int xParitySource = psMetadataLookupS32(&mdokS, source->concepts, "CELL.XPARITY"); // Source parity
+    int xParityTarget = psMetadataLookupS32(&mdokT, target->concepts, "CELL.XPARITY"); // Target x parity
+    assert(mdokS && mdokT);
+    if (abs(xParitySource) != 1 || abs(xParityTarget) != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "CELL.XPARITY is not set for both source (%d) and target (%d)",
+                xParitySource, xParityTarget);
+        psFree(binning);
+        return false;
+    }
+    if (xParityTarget != xParitySource) {
+        xFlip = true;
+    } else {
+        // Use the source parity
+        COPY_CONCEPT(target->concepts, source->concepts, "CELL.XPARITY", S32);
+    }
+
+    int yParityTarget = psMetadataLookupS32(&mdokT, target->concepts, "CELL.YPARITY"); // Target y parity
+    int yParitySource = psMetadataLookupS32(&mdokS, source->concepts, "CELL.YPARITY"); // Source parity
+    assert(mdokS && mdokT);
+    if (abs(yParitySource) != 1 || abs(yParityTarget) != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "CELL.YPARITY is not set for both source (%d) and target (%d)",
+                yParitySource, yParityTarget);
+        psFree(binning);
+        return false;
+    }
+    if (yParityTarget != yParitySource) {
+        yFlip = true;
+    } else {
+        // Use the source parity
+        COPY_CONCEPT(target->concepts, source->concepts, "CELL.YPARITY", S32);
+    }
+    psTrace("psModules.camera", 3, "xFlip: %d; yFlip: %d\n", xFlip, yFlip);
+
+    // Perform deep copy of the images.  I would prefer *not* to do a deep copy, in the interests of speed (we
+    // still need to do another deep copy into the HDU for when we write out), but this is the only way I can
+    // think of to provide security against copying a cell and then unknowingly changing the source when
+    // manipulating the target.
+    for (int i = 0; i < numReadouts; i++) {
+        pmReadout *sourceReadout = sourceReadouts->data[i]; // The source readout
+        pmReadout *targetReadout = pmReadoutAlloc(target); // The target readout; this adds it to the cell
+
+        // Copy attributes
+        // XXX is this correct under binning?
+        targetReadout->col0 = sourceReadout->col0;
+        targetReadout->row0 = sourceReadout->row0;
+        targetReadout->process = sourceReadout->process;
+        targetReadout->file_exists = sourceReadout->file_exists;
+        targetReadout->data_exists = sourceReadout->data_exists;
+
+        // Copy all three image components (image, mask, weight)
+        readoutCopyComponent (&targetReadout->image,  sourceReadout->image,  binning, xFlip, yFlip, pixels);
+        readoutCopyComponent (&targetReadout->mask,   sourceReadout->mask,   binning, xFlip, yFlip, pixels);
+        readoutCopyComponent (&targetReadout->weight, sourceReadout->weight, binning, xFlip, yFlip, pixels);
+
+        // Copy bias
+        while (targetReadout->bias->n > 0) {
+            psListRemove(targetReadout->bias, PS_LIST_HEAD);
+        }
+
+        // Iterate over the biases
+        psListIterator *biasIter = psListIteratorAlloc(sourceReadout->bias, PS_LIST_HEAD, false);
+        psImage *bias = NULL;           // Bias image from iteration
+        while ((bias = psListGetAndIncrement(biasIter))) {
+            psImage *biasCopy = NULL;          // Copy of the bias
+            readoutCopyComponent (&biasCopy, bias, binning, xFlip, yFlip, pixels);
+            psListAdd(targetReadout->bias, PS_LIST_TAIL, biasCopy);
+            psFree(biasCopy);           // Drop reference
+        }
+        psFree(biasIter);
+
+        targetReadout->data_exists = true;
+        psFree(targetReadout);          // Drop reference
+    }
+
+    // Copy the remaining "concepts" over.  Don't copy the TRIMSEC or BIASSEC, since these will be created by
+    // pmHDUGenerate if they don't already exist in the target.  Don't copy the XPARITY or YPARITY, since
+    // we've used those to do the flips.  Don't copy the X0 and Y0 because they are updated below (and are
+    // dependent upon the flips we've done above).
+    psMetadataIterator *conceptsIter = psMetadataIteratorAlloc(source->concepts, PS_LIST_HEAD, NULL);
+    psMetadataItem *conceptItem = NULL; // Item from iteration
+    while ((conceptItem = psMetadataGetAndIncrement(conceptsIter))) {
+        psString name = conceptItem->name; // Name of concept
+        if (!strcmp(name, "CELL.TRIMSEC")) continue;
+        if (!strcmp(name, "CELL.BIASSEC")) continue;
+        if (!strcmp(name, "CELL.XPARITY")) continue;
+        if (!strcmp(name, "CELL.YPARITY")) continue;
+        if (!strcmp(name, "CELL.X0")) continue;
+        if (!strcmp(name, "CELL.Y0")) continue;
+
+        psMetadataItem *copy = psMetadataItemCopy(conceptItem); // Copy of the concept
+        psMetadataAddItem(target->concepts, copy, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(copy);               // Drop reference
+    }
+    psFree(conceptsIter);
+
+    // Need to update CELL.TRIMSEC and CELL.BIASSEC if we changed the binning and they exist already.
+    // XXX this code seems to be very similar to pmConceptsUpdate
+    if ((binning->nXbin != 1) || (binning->nYbin != 1)) {
+        bool mdok = false;
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, target->concepts, "CELL.TRIMSEC"); // The trim section
+        if (mdok && trimsec && !psRegionIsNaN(*trimsec)) {
+            *trimsec = psImageBinningSetRuffRegion(binning, *trimsec);
+            // force integer pixels : truncate x0, roundup x1:
+            trimsec->x0 = (int)trimsec->x0;
+            if (trimsec->x1 > (int)trimsec->x1) {
+                trimsec->x1 = (int)trimsec->x1 + 1;
+            } else {
+                trimsec->x1 = (int)trimsec->x1;
+            }
+            trimsec->y0 = (int)trimsec->y0;
+            if (trimsec->y1 > (int)trimsec->y1) {
+                trimsec->y1 = (int)trimsec->y1 + 1;
+            } else {
+                trimsec->y1 = (int)trimsec->y1;
+            }
+        }
+        psList *biassecs = psMetadataLookupPtr(&mdok, target->concepts, "CELL.BIASSEC"); // The bias sections
+        if (mdok && biassecs && biassecs->n > 0) {
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, true); // Iterator
+            psRegion *biassec = NULL;   // Bias section, from iteration
+            while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                if (!psRegionIsNaN(*biassec)) {
+                    *biassec = psImageBinningSetRuffRegion(binning, *biassec);
+                    // force integer pixels : truncate x0, roundup x1:
+                    biassec->x0 = (int)biassec->x0;
+                    if (biassec->x1 > (int)biassec->x1) {
+                        biassec->x1 = (int)biassec->x1 + 1;
+                    } else {
+                        biassec->x1 = (int)biassec->x1;
+                    }
+                    biassec->y0 = (int)biassec->y0;
+                    if (biassec->y1 > (int)biassec->y1) {
+                        biassec->y1 = (int)biassec->y1 + 1;
+                    } else {
+                        biassec->y1 = (int)biassec->y1;
+                    }
+                }
+            }
+            psFree(biassecsIter);
+        }
+    }
+
+    // Need to update CELL.X0 and CELL.Y0 if we flipped
+    // XXX this section should probably use a common function consistent with psImageBinning
+    if (xFlip) {
+        int xZero = psMetadataLookupS32(NULL, source->concepts, "CELL.X0"); // CELL.X0 from source
+        int xParity = psMetadataLookupS32(NULL, source->concepts, "CELL.XPARITY"); // Parity in x
+        int sourceBin = psMetadataLookupS32(NULL, source->concepts, "CELL.XBIN"); // CELL.XBIN from source
+        int xSize = psMetadataLookupS32(NULL, source->concepts, "CELL.XSIZE"); // CELL.XSIZE of source
+
+        if (sourceBin == 0) {
+            // Don't know the binning; assume it is unity
+            sourceBin = binning->nXbin;
+        }
+
+        // XXX make sure this is consistent with the psImageBinning
+        psTrace("psModules.camera", 3, "CELL.X0: Before: %d After: %d\n", xZero, xZero + (xSize - 1) * xParity * sourceBin);
+        psTrace("psModules.camera", 9, "(xParity: %d xBin: %d numCols: %d)\n", xParity, sourceBin, xSize);
+
+        if (xParity == 0 || xSize == 0) {
+            psWarning("New CELL.X0 may be incorrect due to missing concepts (CELL.XPARITY, CELL.XSIZE)");
+        }
+
+        xZero += (xSize - 1) * xParity * sourceBin; // Change the parity on the X0 position
+        psMetadataItem *newItem = psMetadataLookup(target->concepts, "CELL.X0"); // CELL.X0 from target
+        newItem->data.S32 = xZero;
+    }
+    if (yFlip) {
+        int yZero = psMetadataLookupS32(NULL, source->concepts, "CELL.Y0"); // CELL.Y0 from source
+        int yParity = psMetadataLookupS32(NULL, source->concepts, "CELL.YPARITY"); // Parity in y
+        int sourceBin = psMetadataLookupS32(NULL, source->concepts, "CELL.YBIN"); // Binning in y
+        int ySize = psMetadataLookupS32(NULL, source->concepts, "CELL.YSIZE"); // CELL.YSIZE of source
+
+        if (sourceBin == 0) {
+            // Don't know the binning; assume it is unity
+            sourceBin = binning->nYbin;
+        }
+
+        psTrace("psModules.camera", 3, "CELL.Y0: Before: %d After: %d\n", yZero, yZero + (ySize - 1) * yParity * sourceBin);
+        psTrace("psModules.camera", 9, "(yParity: %d yBin: %d numRows: %d)\n", yParity, sourceBin, ySize);
+
+        if (yParity == 0 || ySize == 0) {
+            psWarning("New CELL.Y0 may be incorrect due to missing concepts "
+                      "(CELL.Y0, CELL.YPARITY, CELL.YBIN, CELL.YSIZE)");
+        }
+
+        yZero += (ySize - 1) * yParity * sourceBin; // Change the parity on the Y0 position
+        psMetadataItem *newItem = psMetadataLookup(target->concepts, "CELL.Y0"); // CELL.Y0 from target
+        newItem->data.S32 = yZero;
+    }
+
+    // Update the binning concepts
+    // XXX this should probably be done with a common function using psImageBinning
+    psMetadataItem *binItem = psMetadataLookup(target->concepts, "CELL.XBIN");
+    binItem->data.S32 *= xBin;
+    binItem = psMetadataLookup(target->concepts, "CELL.YBIN");
+    binItem->data.S32 *= yBin;
+
+    // Copy any headers
+    pmHDU *targetHDU = pmHDUFromCell(target); // The target HDU
+    if (targetHDU) {
+        pmHDU *sourceHDU = pmHDUFromCell(source); // The source HDU
+        if (!sourceHDU) {
+            psWarning("Unable to copy header: no source header.");
+        } else if (sourceHDU->header) {
+            targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+        }
+    }
+
+    // Copy the PHU over as well, if required
+    pmHDU *targetPHU = findBlankPHU(target); // The target PHU
+    if (targetPHU && targetPHU != targetHDU) {
+        // pmHDU *sourcePHU = pmHDUGetHighest(source->parent->parent, source->parent, source); // A source HDU
+        pmHDU *sourcePHU = findBlankPHU(source); // The target PHU
+        if (sourcePHU && sourcePHU->header) {
+            targetPHU->header = psMetadataCopy(targetPHU->header, sourcePHU->header);
+        }
+    }
+
+    psFree (binning);
+    target->data_exists = true;
+    target->parent->data_exists = true;
+    return true;
+}
+
+// Common engine for pmChipCopy and pmChipCopyStructure
+// Iterate on the components
+static bool chipCopy(pmChip *target,          // The target chip
+                     const pmChip *source, // The source chip, to be copied
+                     bool pixels,             // Copy the pixels?
+                     int xBin, int yBin       // (Relative) binning factors in x and y
+                    )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0);
+    assert(yBin > 0);
+
+    if (!source->data_exists) {
+        // Copied everything that exists
+        return true;
+    }
+
+    psArray *targetCells = target->cells; // The target cells
+    psArray *sourceCells = source->cells; // The source cells
+    if (targetCells->n != sourceCells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Number of source cells (%ld) differs from the number of target cells (%ld)\n",
+                sourceCells->n, targetCells->n);
+        return false;
+    }
+
+    bool status = true;                 // Status of copy
+    for (int i = 0; i < targetCells->n; i++) {
+        pmCell *targetCell = targetCells->data[i]; // The target cell
+        const char *cellName = psMetadataLookupStr(NULL, targetCell->concepts, "CELL.NAME"); // Name of cell
+        int cellNum = pmChipFindCell(source, cellName); // Number of cell with that name
+        if (cellNum >= 0) {
+            pmCell *sourceCell = sourceCells->data[cellNum]; // The source cell
+            status &= cellCopy(targetCell, sourceCell, pixels, xBin, yBin);
+        }
+    }
+
+    // Update the concepts
+    psMetadataItem *chipName = psMemIncrRefCounter(psMetadataLookup(target->concepts, "CHIP.NAME"));
+    psMetadataCopy(target->concepts, source->concepts);
+    psMetadataAddItem(target->concepts, chipName, PS_LIST_TAIL, PS_META_REPLACE);
+    psFree(chipName);
+    psMetadataCopy(target->parent->concepts, source->parent->concepts);
+
+    target->data_exists = true;
+    return status;
+}
+
+// create a new pmChip with the data derived from the supplied chip
+pmChip *pmChipDuplicate(pmFPA *fpa, const pmChip *sourceChip)
+{
+    assert(sourceChip);
+
+    bool status;
+    char *chipName = psMetadataLookupStr(&status, sourceChip->concepts, "CHIP.NAME");
+    pmChip *targetChip = pmChipAlloc (NULL, chipName);
+    targetChip->parent = fpa;
+
+    psArray *sourceCells = sourceChip->cells; // The source cells
+
+    for (int i = 0; i < sourceCells->n; i++) {
+        pmCell *sourceCell = sourceCells->data[i]; // The sources cell
+        const char *cellName = psMetadataLookupStr(NULL, sourceCell->concepts, "CELL.NAME"); // Name of cell
+        // XXX are there other concepts I need to copy first?
+        pmCell *targetCell = pmCellAlloc (targetChip, cellName);
+        int xParityTarget = psMetadataLookupS32(&status, sourceCell->concepts, "CELL.XPARITY"); // Target x parity
+        psMetadataAddS32 (targetCell->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "", xParityTarget);
+        int yParityTarget = psMetadataLookupS32(&status, sourceCell->concepts, "CELL.YPARITY"); // Target y parity
+        psMetadataAddS32 (targetCell->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "", yParityTarget);
+        if (!cellCopy(targetCell, sourceCell, true, 1, 1)) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "failed to duplicate chip\n");
+            return NULL;
+        }
+        // update the attributes
+        targetCell->file_exists = sourceCell->file_exists;
+        targetCell->data_exists = sourceCell->data_exists;
+        targetCell->process     = sourceCell->process;
+    }
+
+    // Update the concepts
+    psMetadataCopy(targetChip->concepts, sourceChip->concepts);
+
+    // update the attributes
+    targetChip->file_exists = sourceChip->file_exists;
+    targetChip->data_exists = sourceChip->data_exists;
+    targetChip->process     = sourceChip->process;
+
+    return targetChip;
+}
+
+// Common engine for pmFPACopy and pmFPACopyStructure.
+// Iterate on the components
+static bool fpaCopy(pmFPA *target,      // The target FPA
+                    const pmFPA *source, // The source FPA, to be copied
+                    bool pixels,        // Copy the pixels?
+                    int xBin, int yBin  // (Relative) binning factors in x and y
+                   )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0);
+    assert(yBin > 0);
+
+    psArray *targetChips = target->chips; // The target chips
+    psArray *sourceChips = source->chips; // The source chips
+    if (targetChips->n != sourceChips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Number of source chips (%ld) differs from the number of target chips (%ld)\n",
+                sourceChips->n, targetChips->n);
+        return false;
+    }
+
+    bool status = true;                 // Status of copy
+    for (int i = 0; i < targetChips->n; i++) {
+        pmChip *targetChip = targetChips->data[i]; // The target chip
+        const char *chipName = psMetadataLookupStr(NULL, targetChip->concepts, "CHIP.NAME"); // Name of chip
+        int chipNum = pmFPAFindChip(source, chipName); // Number of chip with that name
+        if (chipNum >= 0) {
+            pmChip *sourceChip = sourceChips->data[chipNum]; // The source chip
+            status &= chipCopy(targetChip, sourceChip, pixels, xBin, yBin);
+        }
+    }
+
+    // Update the concepts
+    psMetadataCopy(target->concepts, source->concepts);
+
+    return status;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmFPACopy(pmFPA *target, const pmFPA *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy FPA onto itself.");
+        return false;
+    }
+    return fpaCopy(target, source, true, 1, 1);
+}
+
+bool pmChipCopy(pmChip *target, const pmChip *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy chip onto itself.");
+        return false;
+    }
+    return chipCopy(target, source, true, 1, 1);
+}
+
+bool pmCellCopy(pmCell *target, const pmCell *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy cell onto itself.");
+        return false;
+    }
+    return cellCopy(target, source, true, 1, 1);
+}
+
+
+bool pmFPACopyStructure(pmFPA *target, const pmFPA *source, int xBin, int yBin)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy FPA onto itself.");
+        return false;
+    }
+    return fpaCopy(target, source, false, xBin, yBin);
+}
+
+bool pmChipCopyStructure(pmChip *target, const pmChip *source, int xBin, int yBin)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy chip onto itself.");
+        return false;
+    }
+    return chipCopy(target, source, false, xBin, yBin);
+}
+
+bool pmCellCopyStructure(pmCell *target, const pmCell *source, int xBin, int yBin)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy cell onto itself.");
+        return false;
+    }
+    return cellCopy(target, source, false, xBin, yBin);
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACopy.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACopy.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPACopy.h	(revision 22290)
@@ -0,0 +1,78 @@
+/* @file pmFPACopy.h
+ * @brief Functions to copy FPA components.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-04-12 02:51:44 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_COPY_H
+#define PM_FPA_COPY_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Copy an FPA and components, including the pixels, to a different representation of the same camera
+///
+/// This function is useful for converting between different representations of the same camera.  For example,
+/// between Megacam "RAW" (one amp per extension) and Megacam "SPLICED" formats (two amps = 1 chip per
+/// extension, spliced together).  Components are spliced together as necessary.
+bool pmFPACopy(pmFPA *target,           ///< The target FPA
+               const pmFPA *source      ///< The source FPA, to be copied
+              );
+
+/// Copy a chip and components, including the pixels, to a different representation of the same camera
+///
+/// This function is useful for converting between different representations of the same camera.  For example,
+/// between Megacam "RAW" (one amp per extension) and Megacam "SPLICED" formats (two amps = 1 chip per
+/// extension, spliced together).  Components are spliced together as necessary.
+bool pmChipCopy(pmChip *target,         ///< The target chip
+                const pmChip *source    ///< The source chip, to be copied
+               );
+
+/// Copy a cell and components, including the pixels, to a different representation of the same camera
+///
+/// This function is useful for converting between different representations of the same camera.  For example,
+/// between Megacam "RAW" (one amp per extension) and Megacam "SPLICED" formats (two amps = 1 chip per
+/// extension, spliced together).  Components are spliced together as necessary.
+bool pmCellCopy(pmCell *target,         ///< The target cell
+                const pmCell *source    ///< The source cell, to be copied
+               );
+
+
+/// Copy an FPA, but not the pixels, to a different representation of the same camera
+///
+/// This function the same as pmFPACopy, except that the pixels are not copied (though images of sufficient
+/// size are allocated in the target).  Changes the CELL.XBIN and CELL.YBIN according to the provided binning
+/// factors.
+bool pmFPACopyStructure(pmFPA *target,   ///< The target FPA
+                        const pmFPA *source, ///< The source FPA, to be copied
+                        int xBin, int yBin ///< Binning factors in x and y
+                       );
+
+/// Copy a chip, but not the pixels, to a different representation of the same camera
+///
+/// This function the same as pmChipCopy, except that the pixels are not copied (though images of sufficient
+/// size are allocated in the target).  Changes the CELL.XBIN and CELL.YBIN according to the provided binning
+/// factors.
+bool pmChipCopyStructure(pmChip *target, ///< The target chip
+                         const pmChip *source, ///< The source chip, to be copied
+                         int xBin, int yBin ///< Binning factors in x and y
+                        );
+
+/// Copy a cell, but not the pixels, to a different representation of the same camera
+///
+/// This function the same as pmCellCopy, except that the pixels are not copied (though images of sufficient
+/// size are allocated in the target).  Changes the CELL.XBIN and CELL.YBIN according to the provided binning
+/// factors.
+bool pmCellCopyStructure(pmCell *target, ///< The target cell
+                         const pmCell *source, ///< The source cell, to be copied
+                         int xBin, int yBin ///< Binning factors in x and y
+                        );
+
+pmChip *pmChipDuplicate(pmFPA *fpa, const pmChip *source);
+
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAExtent.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAExtent.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAExtent.c	(revision 22290)
@@ -0,0 +1,155 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+
+// return cell pixels bounding the readout
+psRegion *pmReadoutExtent(const pmReadout *readout)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+
+    psImage *image = readout->image;    // Image from which to get dimensions
+    if (!image) {
+        image = readout->mask;
+    }
+    if (!image) {
+        image = readout->weight;
+    }
+
+    int xSize = 0;
+    int ySize = 0;
+
+    if (!image) {
+	pmHDU *hdu = pmHDUFromReadout (readout);
+	if (hdu && hdu->header) {
+	    xSize = psMetadataLookupS32(NULL, hdu->header, "NAXIS1");
+	    ySize = psMetadataLookupS32(NULL, hdu->header, "NAXIS2");
+	} else {
+        // Don't have anything to base the true extent on, so have to give the hardwired value (largest possible extent)
+	    xSize = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.XSIZE");
+	    ySize = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.YSIZE");
+	}
+        return psRegionAlloc(0, xSize, 0, ySize);
+    }
+
+    // Get the offset to the CCD window
+    int xWindow = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.XWINDOW");
+    int yWindow = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.YWINDOW");
+    return psRegionAlloc(xWindow, xWindow + image->numCols,
+                         yWindow, yWindow + image->numRows);
+}
+
+// return chip pixels bounding the cell (all readouts)
+psRegion *pmCellExtent(const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psArray *readouts = cell->readouts; // Array of component readouts
+    psRegion *cellExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of cell
+    for (long i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        psRegion *roExtent = pmReadoutExtent(readout); // Extent of readout
+        cellExtent->x0 = PS_MIN(cellExtent->x0, roExtent->x0);
+        cellExtent->x1 = PS_MAX(cellExtent->x1, roExtent->x1);
+        cellExtent->y0 = PS_MIN(cellExtent->y0, roExtent->y0);
+        cellExtent->y1 = PS_MAX(cellExtent->y1, roExtent->y1);
+        psFree(roExtent);
+    }
+
+    bool mdok;                          // Status of MD lookup
+    int x0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0"); // Cell x offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CELL.X0.\n");
+        psFree(cellExtent);
+        return NULL;
+    }
+    int y0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0"); // Cell y offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CELL.Y0.\n");
+        psFree(cellExtent);
+        return NULL;
+    }
+
+    cellExtent->x0 += x0;
+    cellExtent->x1 += x0;
+    cellExtent->y0 += y0;
+    cellExtent->y1 += y0;
+
+    return cellExtent;
+}
+
+// return chip pixels included in all cells
+psRegion *pmChipPixels(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psArray *cells = chip->cells;       // Array of component cells
+    psRegion *chipExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of chip
+    for (long i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *cellExtent = pmCellExtent(cell); // Extent of cell
+        chipExtent->x0 = PS_MIN(chipExtent->x0, cellExtent->x0);
+        chipExtent->x1 = PS_MAX(chipExtent->x1, cellExtent->x1);
+        chipExtent->y0 = PS_MIN(chipExtent->y0, cellExtent->y0);
+        chipExtent->y1 = PS_MAX(chipExtent->y1, cellExtent->y1);
+        psFree(cellExtent);
+    }
+
+    return chipExtent;
+}
+
+// return pixels in basic FPA grid bounded by chip
+// this FPA grid has 0,0 at the 0,0 corner of one chip, and is NOT the same
+// as the astrometry focal plane coordinate system
+psRegion *pmChipExtent(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psRegion *chipExtent = pmChipPixels(chip);
+    if (!chipExtent) return NULL;
+
+    bool mdok;                          // Status of MD lookup
+    int x0 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0"); // Chip x offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CHIP.X0.\n");
+        psFree(chipExtent);
+        return NULL;
+    }
+    int y0 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0"); // Chip y offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CHIP.Y0.\n");
+        psFree(chipExtent);
+        return NULL;
+    }
+
+    chipExtent->x0 += x0;
+    chipExtent->x1 += x0;
+    chipExtent->y0 += y0;
+    chipExtent->y1 += y0;
+
+    return chipExtent;
+}
+
+// return FPA pixels included in all chips
+// this FPA grid has 0,0 at the 0,0 corner of one chip, and is NOT the same
+// as the astrometry focal plane coordinate system
+psRegion *pmFPAPixels(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    psArray *chips = fpa->chips;       // Array of component chips
+    psRegion *fpaExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of fpa
+    for (long i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        psRegion *chipExtent = pmChipExtent(chip); // Extent of chip
+        fpaExtent->x0 = PS_MIN(fpaExtent->x0, chipExtent->x0);
+        fpaExtent->x1 = PS_MAX(fpaExtent->x1, chipExtent->x1);
+        fpaExtent->y0 = PS_MIN(fpaExtent->y0, chipExtent->y0);
+        fpaExtent->y1 = PS_MAX(fpaExtent->y1, chipExtent->y1);
+        psFree(chipExtent);
+    }
+
+    return fpaExtent;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAExtent.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAExtent.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAExtent.h	(revision 22290)
@@ -0,0 +1,49 @@
+/* @file  pmPFAExtent.h
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-21 22:01:32 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_EXTENT_H
+#define PM_FPA_EXTENT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Return the extent of a readout
+///
+/// The extent is determined from an image, if present, or the CELL.TRIMSEC otherwise.
+psRegion *pmReadoutExtent(const pmReadout *readout ///< The readout of interest
+                         );
+
+/// Return the extent of a cell
+///
+/// The extent is determined from the extent of the component readouts, plus CELL.X0,Y0
+psRegion *pmCellExtent(const pmCell *cell ///< The cell of interest
+                      );
+
+// return chip pixels included in all cells
+psRegion *pmChipPixels(const pmChip *chip);
+
+/// Return the extent of a chip
+///
+/// The extent is determined from the extent of the component cells, plus CHIP.X0,Y0
+psRegion *pmChipExtent(const pmChip *chip ///< The chip of interest
+                      );
+
+// return FPA pixels included in all chips
+// this FPA grid has 0,0 at the 0,0 corner of one chip, and is NOT the same
+// as the astrometry focal plane coordinate system
+psRegion *pmFPAPixels(const pmFPA *fpa);
+
+/// Return the extent of an FPA
+///
+/// The extent is determined from the extent of the component chips.
+psRegion *pmFPAExtent(const pmFPA *fpa ///< The FPA of interest
+                     );
+
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAFlags.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAFlags.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAFlags.c	(revision 22290)
@@ -0,0 +1,341 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+
+
+/** functions to turn on/off the file_exists flag **/
+bool pmFPASetFileStatus(pmFPA *fpa, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        pmChipSetFileStatus (chip, status);
+    }
+    return true;
+}
+
+bool pmChipSetFileStatus(pmChip *chip, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    chip->file_exists = status;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        pmCellSetFileStatus (cell, status);
+    }
+    return true;
+}
+
+bool pmCellSetFileStatus(pmCell *cell, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    cell->file_exists = status;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->file_exists = status;
+    }
+    return true;
+}
+
+bool pmFPACheckFileStatus(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!pmChipCheckFileStatus(chip)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool pmChipCheckFileStatus(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    if (!chip->file_exists) {
+        return false;
+    }
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        if (!pmCellCheckFileStatus(cell)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool pmCellCheckFileStatus(const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    if (!cell->file_exists) {
+        return false;
+    }
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        if (!readout->file_exists) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/** functions to turn on/off the data_exists flag **/
+bool pmFPASetDataStatus(pmFPA *fpa, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        pmChipSetDataStatus (chip, status);
+    }
+    return true;
+}
+
+bool pmChipSetDataStatus(pmChip *chip, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    chip->data_exists = status;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        pmCellSetDataStatus (cell, status);
+    }
+    return true;
+}
+
+bool pmCellSetDataStatus (pmCell *cell, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    cell->data_exists = status;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->data_exists = status;
+    }
+    return true;
+}
+
+// pmFPA does not have its own data_exists flag; check its children
+bool pmFPACheckDataStatus (const pmFPA *fpa) {
+
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (chip == NULL) continue;
+        if (chip->data_exists) return true;
+    }
+    return false;
+}
+
+bool pmChipCheckDataStatus (const pmChip *chip) {
+
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    return (chip->data_exists);
+}
+
+bool pmCellCheckDataStatus (const pmCell *cell) {
+
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    return (cell->data_exists);
+}
+
+bool pmReadoutCheckDataStatus (const pmReadout *readout) {
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    return (readout->data_exists);
+}
+
+bool pmFPAviewCheckDataStatus (const pmFPA *fpa, const pmFPAview *view) {
+
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (view->chip == -1) {
+        bool exists = pmFPACheckDataStatus (fpa);
+        return exists;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        bool exists = pmChipCheckDataStatus (chip);
+        return exists;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        bool exists = pmCellCheckDataStatus (cell);
+        return exists;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouds->n == %ld", view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    bool exists = pmReadoutCheckDataStatus (readout);
+    return exists;
+}
+
+// Set cells within a chip to be processed or not
+static bool setCellsProcess(const pmChip *chip, // Chip of interest
+                            bool process  // Process this chip?
+                           )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    psArray *cells = chip->cells;       // Component cells
+    if (! cells) {
+        return false;
+    }
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *tmpCell = cells->data[i]; // Cell of interest
+        if (tmpCell) {
+            tmpCell->process = process;
+        }
+    }
+
+    return true;
+}
+
+
+bool pmFPASelectChip(pmFPA *fpa, int chipNum, bool exclusive)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    psArray *chips = fpa->chips;        // Component chips
+    if ((chips == NULL) || (chipNum >= chips->n)) {
+        return(false);
+    }
+
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) chips->data[i];
+        if (tmpChip == NULL) {
+            continue;
+        }
+        if (i == chipNum) {
+            tmpChip->process = true;
+            setCellsProcess(tmpChip, true);
+        } else {
+            if (exclusive) {
+                tmpChip->process = false;
+                setCellsProcess(tmpChip, false);
+            }
+        }
+
+    }
+
+    return true;
+}
+
+// XXX this function should probably be re-defined to merge with 'setCellsProcess'
+bool pmChipSelectCell(pmChip *chip, int cellNum, bool exclusive)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    psArray *cells = chip->cells;       // Component cells
+    if (!cells || cellNum > cells->n) {
+        return false;
+    }
+
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];
+        if (!cell) {
+            continue;
+        }
+        if (i == cellNum) {
+            cell->process = true;
+        } else {
+            if (exclusive) {
+                cell->process = false;
+            }
+        }
+    }
+    return true;
+}
+
+
+int pmFPAExcludeChip(pmFPA *fpa, int chipNum)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, -1);
+
+    psArray *chips = fpa->chips;        // Component chips
+    if (chips == NULL) {
+        psWarning("WARNING: fpa->chips == NULL\n");
+        return(0);
+    }
+    if ((chipNum >= chips->n) || (NULL == (pmChip *) chips->data[chipNum])) {
+        psWarning("WARNING: the specified chip (%d) does not exist.\n", chipNum);
+        return(0);
+    }
+
+    int numChips = 0;                   // Number of chips to be processed
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) chips->data[i]; // Chip of interest
+        if (tmpChip != NULL) {
+            if (i == chipNum) {
+                tmpChip->process = false;
+                setCellsProcess(tmpChip, false); // Wipe out the cell as well
+            } else if (tmpChip->process) {
+                numChips++;
+            }
+        }
+    }
+
+    return(numChips);
+}
+
+
+int pmChipExcludeCell(pmChip *chip, int cellNum)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, -1);
+
+    psArray *cells = chip->cells;       // The component cells
+    if (!cells || cellNum > cells->n) {
+        return 0;
+    }
+
+    int numCells = 0;                   // Number of cells to be processed
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];
+        if (!cell) {
+            continue;
+        }
+        if (i == cellNum) {
+            cell->process = false;
+        } else {
+            numCells++;
+        }
+    }
+
+    return numCells;
+}
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAFlags.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAFlags.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAFlags.h	(revision 22290)
@@ -0,0 +1,122 @@
+/*  @file pmFPAFlags.h
+ *  @brief Functions for setting and checking the status flags within the FPA hierarchy
+ * 
+ *  @author George Gusciora, MHPCC
+ *  @author Paul Price, IfA
+ *  @author Eugene Magnier, IfA
+ * 
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-05-03 20:04:01 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FLAGS_H
+#define PM_FPA_FLAGS_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// Functions to turn on/off the file_exists flags
+
+/// Set the file_exists flag for an FPA and components
+bool pmFPASetFileStatus(pmFPA *fpa,     ///< FPA for which to set status
+                        bool status     ///< Status to set
+                       );
+
+/// Set the file_exists flag for a chip and components
+bool pmChipSetFileStatus(pmChip *chip,  ///< Chip for which to set status
+                         bool status    ///< Status to set
+                        );
+
+/// Set the file_exists flag for a cell and components
+bool pmCellSetFileStatus(pmCell *cell,  ///< Cell for which to set status
+                         bool status    ///< Status to set
+                        );
+
+// Functions to check the file_exists flags
+
+/// Return the file_exists status for an FPA and components
+bool pmFPACheckFileStatus(const pmFPA *fpa ///< FPA for which to check status
+                         );
+
+/// Return the file_exists status for a chip and components
+bool pmChipCheckFileStatus(const pmChip *chip ///< Chip for which to check status
+                          );
+
+/// Return the file_exists status for a chip and components
+bool pmCellCheckFileStatus(const pmCell *cell ///< Cell for which to check status
+                          );
+
+// Functions to turn on/off the data_exists flags
+
+/// Set the data_exists flag for an FPA and components
+bool pmFPASetDataStatus(pmFPA *fpa,     ///< FPA for which to set status
+                        bool status     ///< Status to set
+                       );
+
+/// Set the data_exists flag for a chip and components
+bool pmChipSetDataStatus(pmChip *chip,  ///< Chip for which to set status
+                         bool status    ///< Status to set
+                        );
+
+/// Set the data_exists flag for a cell and components
+bool pmCellSetDataStatus(pmCell *cell,  ///< Cell for which to set status
+                         bool status    ///< Status to set
+                        );
+
+
+// Functions the check the data_exists flags
+
+/// Check data_exists for the element of this fpa at this view
+bool pmFPAviewCheckDataStatus (const pmFPA *fpa, ///< FPA to check
+			       const pmFPAview *view ///< check for this view 
+  );
+
+/// Check data_exists for this fpa
+bool pmFPACheckDataStatus (const pmFPA *fpa ///< FPA to check
+  );
+
+/// Check data_exists for this chip
+bool pmChipCheckDataStatus (const pmChip *chip ///< Chip to check
+);
+
+/// Check data_exists for this cell
+bool pmCellCheckDataStatus (const pmCell *cell ///< Cell to check
+);
+
+/// Check data_exists for this readout
+bool pmReadoutCheckDataStatus (const pmReadout *readout ///< Readout to check
+);
+
+
+// Functions to set the process flags
+
+/// Select a chip within an FPA for processing
+///
+/// If exclusive is true, the specified chip is the only chip to be processed.  A negative value for chipNum
+/// is valid and, in combinations with exclusive, de-selects all chips.
+bool pmFPASelectChip(pmFPA *fpa,        ///< FPA containing the chip of interest
+                     int chipNum,       ///< Chip number to select
+                     bool exclusive     ///< Process this chip exclusive of the others?
+                    );
+
+/// Select a chip within a chip for processing
+///
+/// If exclusive is true, the specified cell is the only chip to be processed.  A negative value for cellNum
+/// is valid and, in combinations with exclusive, de-selects all chips.
+bool pmChipSelectCell(pmChip *chip,     ///< Chip containing the cell of interest
+                      int cellNum,      ///< Cell number to select
+                      bool exclusive    ///< Process this cell exclusive of the others?
+                     );
+
+/// Exclude a chip within an FPA from processing
+int pmFPAExcludeChip(pmFPA *fpa,        ///< FPA containing the chip of interest
+                     int chipNum        ///< Chip number to exclude
+                    );
+
+/// Exclude a cell within a chip from processing
+int pmChipExcludeCell(pmChip *chip,     ///< Chip containing the chip of interest
+                      int cellNum       ///< Cell number to exclude
+                     );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAHeader.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAHeader.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAHeader.c	(revision 22290)
@@ -0,0 +1,77 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadHeader(pmCell *cell, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!cell->hdu) {
+        return pmChipReadHeader(cell->parent, fits);
+    }
+    if (!pmHDUReadHeader(cell->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    return pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, false, NULL);
+}
+
+
+bool pmChipReadHeader(pmChip *chip, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!chip->hdu) {
+        return pmFPAReadHeader(chip->parent, fits);
+    }
+    if (!pmHDUReadHeader(chip->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for chip.\n");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pmFPAReadHeader(pmFPA *fpa, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!fpa->hdu) {
+        return false;
+    }
+    if (!pmHDUReadHeader(fpa->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for FPA.\n");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAHeader.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAHeader.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAHeader.h	(revision 22290)
@@ -0,0 +1,41 @@
+/*  @file pmFPAHeader.h
+ *  @brief Functions read FITS headers for FPA components
+ * 
+ *  @author Paul Price, IfA
+ * 
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-01-24 02:54:14 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_HEADER_H
+#define PM_FPA_HEADER_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Read the FITS header (and ingest concepts) for an FPA, if it exists at this level
+///
+/// Returns false if there was a problem.  Returns true if it successfully read the header, or if the header
+/// was already there.  No iteration to lower levels is performed.
+bool pmFPAReadHeader(pmFPA *fpa,        ///< FPA for which to read header
+                     psFits *fits       ///< FITS file handle
+                    );
+
+/// Read the FITS header (and ingest concepts) for a chip, if it exists at this level
+///
+/// Returns false if there was a problem.  Returns true if it successfully read the header, or if the header
+/// was already there.  No iteration to lower levels is performed.
+bool pmChipReadHeader(pmChip *chip,     ///< Chip for which to read header
+                      psFits *fits      ///< FITS file handle
+                     );
+
+/// Read the FITS header (and ingest concepts) for a cell, if it exists at this level
+///
+/// Returns false if there was a problem.  Returns true if it successfully read the header, or if the header
+/// was already there.  No iteration to lower levels is performed.
+bool pmCellReadHeader(pmCell *cell,     ///< Cell for which to read header
+                      psFits *fits      ///< FITS file handle
+                     );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPALevel.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPALevel.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPALevel.c	(revision 22290)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmFPALevel.h"
+
+static const char *NameNONE = "NONE";   ///< Name for PM_FPA_LEVEL_NONE
+static const char *NameFPA = "FPA";     ///< Name for PM_FPA_LEVEL_FPA
+static const char *NameCHIP = "CHIP";   ///< Name for PM_FPA_LEVEL_CHIP
+static const char *NameCELL = "CELL";   ///< Name for PM_FPA_LEVEL_CELL
+static const char *NameREADOUT = "READOUT"; ///< Name for PM_FPA_LEVEL_READOUT
+
+const char *pmFPALevelToName(pmFPALevel level)
+{
+    switch (level) {
+    case PM_FPA_LEVEL_NONE:
+        return NameNONE;
+    case PM_FPA_LEVEL_FPA:
+        return NameFPA;
+    case PM_FPA_LEVEL_CHIP:
+        return NameCHIP;
+    case PM_FPA_LEVEL_CELL:
+        return NameCELL;
+    case PM_FPA_LEVEL_READOUT:
+        return NameREADOUT;
+    default:
+        psAbort("You can't get here; level = %d", level);
+    }
+    return NULL;
+}
+
+pmFPALevel pmFPALevelFromName(const char *name)
+{
+    pmFPALevel val;
+
+    if (name == NULL) {
+        val = PM_FPA_LEVEL_NONE;
+    } else if (!strcasecmp(name, "FPA"))     {
+        val = PM_FPA_LEVEL_FPA;
+    } else if (!strcasecmp(name, "CHIP"))    {
+        val = PM_FPA_LEVEL_CHIP;
+    } else if (!strcasecmp(name, "CELL"))    {
+        val = PM_FPA_LEVEL_CELL;
+    } else if (!strcasecmp(name, "READOUT")) {
+        val = PM_FPA_LEVEL_READOUT;
+    } else if (!strcasecmp(name, "NONE")) {
+        val = PM_FPA_LEVEL_NONE;
+    } else {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unrecognised FPA level name: %s", name);
+        val = PM_FPA_LEVEL_NONE;
+    }
+
+    return val;
+}
+
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPALevel.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPALevel.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPALevel.h	(revision 22290)
@@ -0,0 +1,35 @@
+/* @file pmFPALevel.h
+ * @brief Defines enum and string representations for the FPA levels
+ *
+ * @author Eugene Magnier, MHPCC
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:14 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_LEVEL_H
+#define PM_FPA_LEVEL_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Specify the level of the FPA hierarchy
+typedef enum {
+    PM_FPA_LEVEL_NONE,                  ///< No particular level specified
+    PM_FPA_LEVEL_FPA,                   ///< Level corresponds to an FPA
+    PM_FPA_LEVEL_CHIP,                  ///< Level corresponds to a Chip
+    PM_FPA_LEVEL_CELL,                  ///< Level corresponds to a Cell
+    PM_FPA_LEVEL_READOUT                ///< Level corresponds to a Readout
+} pmFPALevel;
+
+
+/// Return the string representation of the FPA level
+const char *pmFPALevelToName(pmFPALevel level ///< Level enum
+                            );
+
+/// Return the enum representation of the FPA level
+pmFPALevel pmFPALevelFromName(const char *name ///< Level name
+                             );
+/// @}
+#endif // #ifndef PM_FPA_LEVEL_H
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMaskWeight.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMaskWeight.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMaskWeight.c	(revision 22290)
@@ -0,0 +1,312 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmHDUGenerate.h"
+#include "pmFPAMaskWeight.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Create the parent mask images that reside in the HDU
+static void createParentMasks(pmHDU *hdu // The HDU for which to create
+                             )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *masks = hdu->masks;        // Array of masks
+    if (!masks) {
+        masks = psArrayAlloc(images->n);
+        hdu->masks = masks;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || masks->data[i]) {
+            continue;
+        }
+        masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+        psImageInit(masks->data[i], 0);
+    }
+
+    return;
+}
+
+// Create the parent weight images that reside in the HDU
+static void createParentWeights(pmHDU *hdu // The HDU for which to create
+                               )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    if (!weights) {
+        weights = psArrayAlloc(images->n);
+        hdu->weights = weights;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || weights->data[i]) {
+            continue;
+        }
+        weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+    }
+
+    return;
+}
+
+// Identify a readout within the HDU, on the basis of the image pointer.
+// This is a little dirty, but hopefully should work....
+static long identifyReadout(pmHDU *hdu, // The HDU containing the readouts
+                            pmReadout *readout // The readout to be identified
+                           )
+{
+    assert(hdu);
+    assert(readout);
+
+    long index = -1;                    // Index of the readout
+    for (long i = 0; i < hdu->images->n && index == -1; i++) {
+        if (hdu->images->data[i] == readout->image->parent) {
+            index = i;
+        }
+    }
+
+    return index;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutSetMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float saturation = psMetadataLookupF32(&mdok, cell->concepts, "CELL.SATURATION"); // Saturation level
+    if (!mdok || isnan(saturation)) {
+        psError(PS_ERR_IO, true, "CELL.SATURATION is not set --- unable to set mask.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok || isnan(bad)) {
+        psError(PS_ERR_IO, true, "CELL.BAD is not set --- unable to set mask.\n");
+        return false;
+    }
+    psTrace("psModules.camera", 5, "Saturation: %f, bad: %f\n", saturation, bad);
+
+
+    // Set up the mask
+    psImage *image = readout->image;    // The image pixels
+    if (!readout->mask) {
+        // Generate a (throwaway) mask image, if required
+        readout->mask = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+    }
+    psImage *mask = readout->mask;      // The mask pixels
+    psImageInit(mask, 0);
+
+    // Dereference pointers for speed
+    psF32 **imageData = image->data.F32;// The image
+    psU8  **maskData  = mask->data.U8;  // The mask
+
+    for (int i = 0; i < image->numRows; i++) {
+        for (int j = 0; j < image->numCols; j++) {
+            if (imageData[i][j] >= saturation) {
+                maskData[i][j] |= satMask;
+            }
+            if (imageData[i][j] <= bad) {
+                maskData[i][j] |= badMask;
+            }
+            if (!isfinite(imageData[i][j])) {
+                maskData[i][j] |= badMask;
+            }
+        }
+    }
+
+    return true;
+}
+
+// XXX this function creates the mask pixels, or uses the existing mask
+// pixels.  currently, it will set mask bits if (value <= BAD) or (value >= SATURATION)
+// should we optionally ignore these tests?
+bool pmReadoutGenerateMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the mask image if required
+    if (!readout->mask) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set mask.\n");
+            return false;
+        }
+
+        pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+        PS_ASSERT_PTR_NON_NULL(hdu, false);
+        if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+            return false;
+        }
+
+        createParentMasks(hdu);
+
+        // Need to identify which readout we're working with....
+        long index = identifyReadout(hdu, readout); // Index of the readout
+        if (index == -1) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+            return false;
+        }
+
+        psImage *mask = psImageSubset(hdu->masks->data[index], *trimsec); // The mask pixels
+        if (!mask) {
+            psString trimsecString = psRegionToString(*trimsec);
+            psError(PS_ERR_UNKNOWN, false, "Unable to set mask from HDU with trimsec: %s.\n", trimsecString);
+            psFree(trimsecString);
+            return false;
+        }
+        psImageInit(mask, 0);
+        assert (readout->mask == NULL); // or else this is a memory leak.
+        readout->mask = mask;
+    }
+
+    return pmReadoutSetMask(readout, satMask, badMask);
+}
+
+bool pmReadoutSetWeight(pmReadout *readout, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float gain = psMetadataLookupF32(&mdok, cell->concepts, "CELL.GAIN"); // Cell gain
+    if (!mdok || isnan(gain)) {
+        psError(PS_ERR_IO, true, "CELL.GAIN is not set --- unable to set weight.\n");
+        return false;
+    }
+    float readnoise = psMetadataLookupF32(&mdok, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    if (!mdok || isnan(readnoise)) {
+        psError(PS_ERR_IO, true, "CELL.READNOISE is not set --- unable to set weight.\n");
+        return false;
+    }
+
+    if (poisson) {
+        // Set weight image to the variance in ADU = f/g + rn^2
+        psImage *image = readout->image;    // The image pixels
+        readout->weight = (psImage*)psBinaryOp(readout->weight, image, "/", psScalarAlloc(gain, PS_TYPE_F32));
+
+        // a negative weight is non-sensical. if the image value drops below 1, the weight must be 1.
+        readout->weight = (psImage*)psUnaryOp(readout->weight, readout->weight, "abs");
+        readout->weight = (psImage*)psBinaryOp(readout->weight, readout->weight, "max",
+                                               psScalarAlloc(1, PS_TYPE_F32));
+    } else {
+        // Just use the read noise
+        if (!readout->weight) {
+            readout->weight = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+        }
+        psImageInit(readout->weight, 0.0);
+    }
+
+    readout->weight = (psImage*)psBinaryOp(readout->weight, readout->weight, "+",
+                                           psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+
+    return true;
+}
+
+// this function creates the weight pixels, or uses the existing weight pixels.  it will set
+// the noise pixel values only if the weight image is not supplied
+bool pmReadoutGenerateWeight(pmReadout *readout, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the weight image if required
+    if (readout->weight)
+        return true;
+
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+    if (!mdok || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set weight.\n");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+        return false;
+    }
+
+    createParentWeights(hdu);
+
+    // Need to identify which readout we're working with....
+    long index = identifyReadout(hdu, readout); // Index of the readout
+    if (index == -1) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+        return false;
+    }
+
+    psImage *weight = psImageSubset(hdu->weights->data[index], *trimsec); // The weight pixels
+    if (!weight) {
+        psString trimsecString = psRegionToString(*trimsec);
+        psError(PS_ERR_UNKNOWN, false, "Unable to set weight from HDU with trimsec: %s.\n",
+                trimsecString);
+        psFree(trimsecString);
+        return false;
+    }
+    psImageInit(weight, 0);
+    readout->weight = weight;
+
+    return pmReadoutSetWeight(readout, poisson);
+}
+
+bool pmReadoutGenerateMaskWeight(pmReadout *readout, psMaskType satMask, psMaskType badMask, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    bool success = true;                // Was everything successful?
+
+    success &= pmReadoutGenerateMask(readout, satMask, badMask);
+    success &= pmReadoutGenerateWeight(readout, poisson);
+
+    return success;
+}
+
+bool pmCellGenerateMaskWeight(pmCell *cell, psMaskType satMask, psMaskType badMask, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    bool success = true;                // Was everything successful?
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout
+        pmReadoutGenerateMaskWeight(readout, poisson, satMask, badMask);
+    }
+
+    return success;
+}
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMaskWeight.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMaskWeight.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMaskWeight.h	(revision 22290)
@@ -0,0 +1,96 @@
+/* @file pmFPAHeader.h
+ * @brief Functions read FITS headers for FPA components
+ *
+ * @author Paul Price, IfA
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-02 03:51:03 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_MASK_WEIGHT_H
+#define PM_FPA_MASK_WEIGHT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+#if 0
+/// Pixel mask values
+typedef enum {
+    PM_MASK_CLEAR    = 0x00,            ///< The pixel is not masked
+    PM_MASK_BLANK    = 0x01,            ///< The pixel is blank or has no (valid) data
+    PM_MASK_FLAT     = 0x02,            ///< The pixel is non-positive in the flat-field
+    PM_MASK_DETECTOR = 0x02,            ///< The detector pixel is bad (e.g., bad column, charge trap)
+    PM_MASK_SAT      = 0x04,            ///< The pixel is saturated in the image of interest
+    PM_MASK_BAD      = 0x04,            ///< The pixel is low in the image of interest
+    PM_MASK_RANGE    = 0x04,            ///< The pixel is out of range in the image of interest
+    PM_MASK_CR       = 0x08,            ///< The pixel is probably a CR
+    PM_MASK_SPARE1   = 0x10,            ///< Spare mask value
+    PM_MASK_SPARE2   = 0x20,            ///< Spare mask value
+    PM_MASK_SUSPECT  = 0x40,            ///< The pixel is suspected of being bad, but may not be
+    PM_MASK_MARK     = 0x80,            ///< The pixel is marked as temporarily ignored
+} pmMaskValue;
+#else
+#define PM_MASK_MARK 0x80            ///< The pixel is marked as temporarily ignored
+#define PM_MASK_SAT  0x04            ///< The pixel is saturated in the image of interest
+#endif
+
+/// Set a temporary readout mask using CELL.SATURATION and CELL.BAD
+///
+/// Identifies pixels that are saturated (>= CELL.SATURATION) or bad (<= CELL.BAD).  The mask that is produced
+/// within the readout is temporary --- it is not added to the HDU.  This is intended for when the user is
+/// iterating using pmReadoutReadNext, in which case the HDU can't be generated.
+bool pmReadoutSetMask(pmReadout *readout, ///< Readout for which to set mask
+                      psMaskType sat,   ///< Mask value to give saturated pixels
+                      psMaskType bad    ///< Mask value to give bad (low) pixels
+    );
+
+
+/// Set a temporary readout weight map using CELL.GAIN and CELL.READNOISE
+///
+/// Calculates weights (actually variances) for each pixel using photon statistics and the cell gain
+/// (CELL.GAIN) and read noise (CELL.READNOISE).  The weight map that is produced within the readout is
+/// temporary --- it is not added to the HDU.  This is intended for when the user is iterating using
+/// pmReadoutReadNext, in which case the HDU can't be generated.
+bool pmReadoutSetWeight(pmReadout *readout, ///< Readout for which to set weight
+                        bool poisson    ///< Use poisson weights (in addition to read noise)?
+                       );
+
+/// Generate a readout mask (suitable for output) using CELL.SATURATION and CELL.BAD
+///
+/// Identifies pixels that are saturated (>= CELL.SATURATION) or bad (<= CELL.BAD).  The mask that is produced
+/// is suitable for output (complete with HDU entry).  This is intended for most operations.
+bool pmReadoutGenerateMask(pmReadout *readout, ///< Readout for which to generate mask
+                           psMaskType sat, ///< Mask value to give saturated pixels
+                           psMaskType bad ///< Mask value to give bad (low) pixels
+    );
+
+/// Generate a weight map (suitable for output) using CELL.GAIN and CELL.READNOISE
+///
+/// Calculates weights (actually variances) for each pixel using photon statistics and the cell gain
+/// (CELL.GAIN) and read noise (CELL.READNOISE).  The weight map that is produced within the readout is
+/// suitable for output (complete with HDU entry).  This is intended for most operations.
+bool pmReadoutGenerateWeight(pmReadout *readout, ///< Readout for which to generate weight
+                             bool poisson    ///< Use poisson weights (in addition to read noise)?
+                            );
+
+/// Generate mask and weight map for a readout
+///
+/// Calls pmReadoutGenerateMask and pmReadoutGenerateWeight for the readout
+bool pmReadoutGenerateMaskWeight(pmReadout *readout, ///< Readout for which to generate mask and weights
+                                 psMaskType sat, ///< Mask value to give saturated pixels
+                                 psMaskType bad, ///< Mask value to give bad (low) pixels
+                                 bool poisson ///< Use poisson weights (in addition to read noise)?
+                                );
+
+/// Generate mask and weight maps for all readouts within a cell
+///
+/// Calls pmReadoutGenerateMaskWeight for each readout within the cell.
+bool pmCellGenerateMaskWeight(pmCell *cell, ///< Cell for which to generate mask and weights
+                              psMaskType sat, ///< Mask value to give saturated pixels
+                              psMaskType bad, ///< Mask value to give bad (low) pixels
+                              bool poisson ///< Use poisson weights (in addition to read noise)?
+                             );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMosaic.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMosaic.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMosaic.c	(revision 22290)
@@ -0,0 +1,1358 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmConceptsAverage.h"
+#include "pmHDUUtils.h"
+#include "pmConfig.h"
+#include "pmAstrometryWCS.h"
+#include "pmFPAExtent.h"
+
+#include "pmFPAMosaic.h"
+
+
+#define CELL_LIST_BUFFER 10             // Buffer size for cell lists
+
+#define BLANK_VALUE 0.0                 // Value for pixels that are blank in the mosaicked image (e.g., //
+                                        // between cells).
+                                        // XXX This should ultimately be set to NAN, but psphot doesn't like
+                                        // that (masking needs to be more thorough).
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Do two regions overlap?
+#define REGIONS_OVERLAP(region1, region2) \
+((region1->x0 > region2->x0 && region1->x0 < region2->x1) || \
+ (region1->x1 > region2->x0 && region1->x1 < region2->x1) || \
+ (region1->y0 > region2->y0 && region1->y0 < region2->y1) || \
+ (region1->y1 > region2->y0 && region1->y1 < region2->y1))
+
+// Compare a value with a maximum and minimum
+#define COMPARE(value,min,max) \
+if ((value) < (min)) { \
+    (min) = (value); \
+} \
+if ((value) > (max)) { \
+    (max) = (value); \
+}
+
+// Update a concept to the assumed value
+#define FIX_CONCEPT(SOURCE, NAME, TYPE, VALUE) \
+psMetadataItem *item = psMetadataLookup(SOURCE, NAME); \
+item->data.TYPE = VALUE;
+
+// Get the bounds for an chip's pixels on the HDU
+static bool chipBounds(psRegion *bounds, // The bounds for the chip
+                       const pmChip *chip // The chip to examine for contiguity
+                      )
+{
+    assert(chip);
+
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+
+        if (trimsec->x0 < bounds->x0) {
+            bounds->x0 = trimsec->x0;
+        }
+        if (trimsec->x1 > bounds->x1) {
+            bounds->x1 = trimsec->x1;
+        }
+        if (trimsec->y0 < bounds->y0) {
+            bounds->y0 = trimsec->y0;
+        }
+        if (trimsec->y1 > bounds->y1) {
+            bounds->y1 = trimsec->y1;
+        }
+    }
+
+    return true;
+}
+
+// Make sure the TRIMSEC doesn't overlap with the established image bounds
+static bool chipContiguousTrimsec(psRegion *bounds, // The bounds of the image, altered if primary==true
+                                  const pmChip *chip // The chip to examine for contiguity
+                                 )
+{
+    assert(bounds);
+    assert(chip);
+
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+
+        if (REGIONS_OVERLAP(trimsec, bounds)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Make sure the BIASSEC doesn't overlap with the established image bounds
+static bool chipContiguousBiassec(psRegion *bounds, // The bounds of the image, altered if primary==true
+                                  const pmChip *chip // The chip to examine for contiguity
+                                 )
+{
+    assert(bounds);
+    assert(chip);
+
+    // Check that the biases don't get in the way
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (!mdok || !biassecs) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.BIASSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+        if (biassecs->n == 0) {
+            // No point allocating an iterator if there's nothing there to iterate on
+            continue;
+        }
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;       // Bias section from iteration
+        while ((biassec = psListGetAndIncrement(biassecsIter))) {
+            if (psRegionIsNaN(*biassec)) {
+                continue;
+            }
+            if (REGIONS_OVERLAP(biassec, bounds)) {
+                psFree(biassecsIter);
+                return false;
+            }
+        }
+        psFree(biassecsIter);
+    }
+
+    // If we've gotten this far, everything is fine.
+    return true;
+}
+
+// Are the pixels for the FPA contiguous on the HDU?
+// Work this out by examining all the CELL.TRIMSEC and CELL.BIASSEC regions for the component cells
+static bool fpaContiguous(psRegion *bounds, // The bounds of the image, returned
+                          const pmFPA *fpa // The FPA to examine for contiguity
+                         )
+{
+    assert(bounds);
+    assert(fpa);
+
+    *bounds = psRegionSet(INFINITY, 0, INFINITY, 0);
+
+    // Get the size of the pixels on the HDU
+    psArray *chips = fpa->chips;        // The array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chipBounds(bounds, chip)) {
+            return false;
+        }
+    }
+
+    // Make sure the bias regions don't get in the way of the HDU
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chipContiguousBiassec(bounds, chip)) {
+            return false;
+        }
+    }
+
+    // If we got through it all, they must all be contiguous
+    return true;
+}
+
+
+
+// Check a cell for niceness in the parity and binning
+static bool niceCellParityBinning(int *xBin, int *yBin, // Binning for cell, to be returned
+                                  const pmCell *cell // Cell to check for niceness
+                                 )
+{
+    assert(xBin);
+    assert(yBin);
+    assert(cell);
+
+    // A "nice" cell must have only a single readout
+    if (cell->readouts->n != 1) {
+        return false;
+    }
+
+    // A "nice" cell must have parity == 1
+    bool mdok = true;                   // Status of MD lookup
+    int xParity = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY"); // Parity in x
+    if (!mdok || xParity == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    if (xParity != 1) {
+        return false;
+    }
+    int yParity = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY"); // Parity in y
+    if (!mdok || yParity == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    if (yParity != 1) {
+        return false;
+    }
+
+    // A "nice" cell must have consistent binning
+    int xBinCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN"); // Binning in x
+    if (!mdok || xBin <= 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XBIN hasn't been set for cell.\n");
+        return false;
+    }
+    int yBinCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN"); // Binning in y
+    if (!mdok || yBin <= 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YBIN hasn't been set for cell.\n");
+        return false;
+    }
+    if (*xBin == 0 || *yBin == 0) {
+        *xBin = xBinCell;
+        *yBin = yBinCell;
+    } else if (xBinCell != *xBin || yBinCell != *yBin) {
+        return false;
+    }
+
+    return true;
+}
+
+
+// Check a cell for niceness in the boundaries
+static bool niceCellBounds(const pmCell *cell, // Cell to check for niceness
+                           const psRegion *imageBounds // Bounds of the image on the HDU
+                          )
+{
+    // A "nice" cell must have the (0,0) pixel at CELL.X0,CELL.Y0
+    bool mdok = true;                   // Status of MD lookup
+    int x0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0"); // Position of (0,0) on chip
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.X0 hasn't been set for cell.\n");
+        return false;
+    }
+    int y0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0"); // Position of (0,0) on chip
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 hasn't been set for cell.\n");
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[0]; // A representative readout
+    if (!readout) {
+        return false;                   // Nothing here
+    }
+    if (x0 != readout->col0 + readout->image->col0 - (int)imageBounds->x0 ||
+            y0 != readout->row0 + readout->image->row0 - (int)imageBounds->y0) {
+        psTrace("psModules.camera", 5, "CELL.X0,Y0 don't match: %d,%d vs %d,%d\n", x0, y0,
+                readout->col0 + readout->image->col0 - (int)imageBounds->x0,
+                readout->row0 + readout->image->row0 - (int)imageBounds->y0);
+        return false;
+    }
+
+    return true;
+}
+
+
+// Is the chip "nice"?  If so, return the region containing the chip pixels
+static psRegion *niceChip(int *xBinChip, int *yBinChip, // Binning for chip, to be returned
+                          const pmChip *chip // Chip to examine for "niceness".
+                         )
+{
+    assert(xBinChip);
+    assert(yBinChip);
+    assert(chip);
+
+    // Check that we've got the HDU in the chip or the FPA
+    if ((!chip->hdu || !chip->hdu->images) && (!chip->parent->hdu || !chip->parent->hdu->images)) {
+        return NULL;
+    }
+
+    // Check parity and binning for component cells
+    *xBinChip = 0;
+    *yBinChip = 0;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i]; // The cell of interest
+        if (!niceCellParityBinning(xBinChip, yBinChip, cell)) {
+            return NULL;
+        }
+    }
+
+    // Now check that the pixels are all contiguous
+    psRegion *imageBounds = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Bound of image on HDU
+    if (!chipBounds(imageBounds, chip) || !chipContiguousBiassec(imageBounds, chip)) {
+        psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+        psFree(imageBounds);
+        return NULL;
+    }
+
+    psString region = psRegionToString(*imageBounds);
+    psTrace("psModules.camera", 7, "Image bounds: %s\n", region);
+    psFree(region);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i]; // The cell of interest
+        if (!niceCellBounds(cell, imageBounds)) {
+            psFree(imageBounds);
+            return NULL;
+        }
+    }
+
+    // Need to check all the other chips if the HDU is in the FPA
+    pmFPA *fpa = chip->parent;          // The parent FPA
+    if (fpa->hdu && fpa->hdu->images) {
+        psArray *chips = fpa->chips;    // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *testChip = chips->data[i]; // The chip of interest
+            if (testChip == chip) {
+                // Already done this one
+                continue;
+            }
+            if (!chipContiguousTrimsec(imageBounds, testChip) ||
+                    !chipContiguousBiassec(imageBounds, testChip)) {
+                psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+                psFree(imageBounds);
+                return NULL;
+            }
+        }
+    }
+
+    return imageBounds;
+}
+
+// Is the FPA "nice"?  If so, return the region containing the FPA pixels
+static psRegion *niceFPA(int *xBinFPA, int *yBinFPA, // Binning for FPA, to be returned
+                         const pmFPA *fpa  // FPA to examine for "niceness".
+                        )
+{
+    assert(xBinFPA);
+    assert(yBinFPA);
+    assert(fpa);
+
+    // Check that we've got the HDU in the chip or the FPA
+    if (!fpa->hdu || !fpa->hdu->images) {
+        return NULL;
+    }
+
+    // Check parity and binning for component cells
+    *xBinFPA = 0;
+    *yBinFPA = 0;
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i]; // The chip of interest
+        for (int j = 0; i < chip->cells->n; i++) {
+            pmCell *cell = chip->cells->data[j]; // The cell of interest
+            if (!niceCellParityBinning(xBinFPA, yBinFPA, cell)) {
+                return NULL;
+            }
+        }
+    }
+
+    // Now check that the pixels are all contiguous
+    psRegion *imageBounds = psRegionAlloc(0, 0, 0, 0); // Bound of image on HDU
+    if (!fpaContiguous(imageBounds, fpa)) {
+        psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+        psFree(imageBounds);
+        return NULL;
+    }
+
+    psString region = psRegionToString(*imageBounds);
+    psTrace("psModules.camera", 7, "Image bounds: %s\n", region);
+    psFree(region);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i]; // The chip of interest
+        for (int j = 0; i < chip->cells->n; i++) {
+            pmCell *cell = chip->cells->data[j]; // The cell of interest
+            if (!niceCellBounds(cell, imageBounds)) {
+                psFree(imageBounds);
+                return NULL;
+            }
+        }
+    }
+
+    return imageBounds;
+}
+
+// supporting macros used by imageMosaic()
+// copy pixels without binning
+#define COPY_WITH_PARITY_DIFFERENCE(TYPE) \
+        case PS_TYPE_##TYPE: { \
+                for (int y = 0; y < image->numRows; y++) { \
+                    int yTarget =  yTargetBase + yParity * y; \
+                    for (int x = 0; x < image->numCols; x++) { \
+                        int xTarget = xTargetBase + xParity * x; \
+                        mosaic->data.TYPE[yTarget][xTarget] = image->data.TYPE[y][x]; \
+                    } \
+                } \
+            } \
+            break;
+
+// In case the original image is binned but the mosaic is not, we need to fill in the values in
+// the mosaic.  this operation should be replaced with a call to one of the functions defined
+// in psImageBinning
+#define FILL_IN(TYPE) \
+        case PS_TYPE_##TYPE: \
+            for (int y = 0; y < image->numRows; y++) { \
+                float yTargetBinBase = yTargetBase + yParity * yBinSource->data.S32[i] * y / yBinTarget; \
+                for (int x = 0; x < image->numCols; x++) { \
+                    float xTargetBinBase = xTargetBase + xParity * xBinSource->data.S32[i] * x / xBinTarget; \
+                    for (int j = 0; j < yBinSource->data.S32[i]; j++) { \
+                        int yTarget = (int)(yTargetBinBase + yParity * (float)j / (float)yBinTarget); \
+                        for (int k = 0; k < xBinSource->data.S32[i]; k++) { \
+                            int xTarget = (int)(xTargetBinBase + xParity * (float)k / (float)xBinTarget); \
+                            mosaic->data.TYPE[yTarget][xTarget] = image->data.TYPE[y][x]; \
+                        } \
+                    } \
+                } \
+            } \
+            break;
+
+// Mosaic multiple images, with flips, binning and offsets
+static psImage *imageMosaic(const psArray *source, // Images to splice in
+                            const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                            const psVector *xBinSource, // Binning in x of source images
+                            const psVector *yBinSource, // Binning in y of source images
+                            int xBinTarget, int yBinTarget, // Binning in x and y of target images
+                            const psVector *x0, const psVector *y0, // Offsets for source images on target
+                            double unexposed // Value for unexposed pixels
+                           )
+{
+    assert(source);
+    assert(xFlip && xFlip->type.type == PS_TYPE_U8);
+    assert(yFlip && yFlip->type.type == PS_TYPE_U8);
+    assert(xBinSource && xBinSource->type.type == PS_TYPE_S32);
+    assert(yBinSource && yBinSource->type.type == PS_TYPE_S32);
+    assert(x0 && x0->type.type == PS_TYPE_S32);
+    assert(y0 && y0->type.type == PS_TYPE_S32);
+    assert(xFlip->n == source->n);
+    assert(yFlip->n == source->n);
+    assert(xBinSource->n == source->n);
+    assert(yBinSource->n == source->n);
+    assert(x0->n == source->n);
+    assert(y0->n == source->n);
+
+    if (source->n == 0) {
+        return NULL;
+    }
+
+    // Get the maximum extent of the mosaic image
+    int xMin = +INT_MAX;
+    int xMax = -INT_MAX;
+    int yMin = +INT_MAX;
+    int yMax = -INT_MAX;
+    psElemType type = 0;
+    int numImages = 0;                  // Number of images
+    psTrace("psModules.camera", 3, "Mosaicking %ld cells.\n", source->n);
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+        numImages++;
+
+        // Only implemented for F32 and U8 images so far.
+        assert(image->type.type == PS_TYPE_F32 || image->type.type == PS_TYPE_U8);
+        // All input types must be the same
+        if (type == 0) {
+            type = image->type.type;
+        }
+        assert(type == image->type.type);
+
+        // Size of cell in x and y
+        int xParity = xFlip->data.U8[i] ? -1 : 1;
+        int yParity = yFlip->data.U8[i] ? -1 : 1;
+        psTrace("psModules.camera", 5, "Extent of cell %d: %d -> %d , %d -> %d\n", i, x0->data.S32[i],
+                x0->data.S32[i] + xParity * xBinSource->data.S32[i] * image->numCols, y0->data.S32[i],
+                y0->data.S32[i] + yParity * yBinSource->data.S32[i] * image->numRows);
+
+        COMPARE(x0->data.S32[i], xMin, xMax);
+        COMPARE(y0->data.S32[i], yMin, yMax);
+        // Subtract the parity to get the inclusive limit (not exclusive)
+        COMPARE(x0->data.S32[i] + xParity * xBinSource->data.S32[i] * image->numCols - xParity, xMin, xMax);
+        COMPARE(y0->data.S32[i] + yParity * yBinSource->data.S32[i] * image->numRows - yParity, yMin, yMax);
+    }
+    if (numImages == 0) {
+        return NULL;
+    }
+
+    // Set up the image
+    // Since both upper and lower values are inclusive, we need to add one to the size
+    float xSize = (float)(xMax - xMin + 1) / (float)xBinTarget;
+    if (xSize - (int)xSize > 0) {
+        xSize += 1;
+    }
+    float ySize = (float)(yMax - yMin + 1) / (float)yBinTarget;
+    if (ySize - (int)ySize > 0) {
+        ySize += 1;
+    }
+
+    psTrace("psModules.camera", 3, "Spliced image will be %dx%d\n", (int)xSize, (int)ySize);
+    psImage *mosaic = psImageAlloc((int)xSize, (int)ySize, type); // The mosaic image
+    psImageInit(mosaic, unexposed);
+
+    // Next pass through the images to do the mosaicking
+    // XXX this function uses summing for the output: is this the right choice?
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+        int xParity = xFlip->data.U8[i] ? -1 : 1; // Parity difference, in x
+        int yParity = yFlip->data.U8[i] ? -1 : 1; // Parity difference, in y
+        int xTargetBase = (x0->data.S32[i] - xMin) / xBinTarget; // The base x position in the target frame
+        int yTargetBase = (y0->data.S32[i] - yMin) / yBinTarget; // The base y position in the target frame
+
+        // in the first case, we are just copy a section pixel-by-pixel
+        if ((xBinSource->data.S32[i] == xBinTarget) &&
+            (yBinSource->data.S32[i] == yBinTarget) &&
+            (xFlip->data.U8[i] == 0) &&
+            (yFlip->data.U8[i] == 0)) {
+            // Let someone else do the hard work
+            psImageOverlaySection(mosaic, image, xTargetBase, yTargetBase, "=");
+            continue;
+        }
+
+        // in the second case, there's a difference with the parities, but we don't have to
+        // worry about binning
+        if (xBinSource->data.S32[i] == xBinTarget && yBinSource->data.S32[i] == yBinTarget) {
+            switch (type) {
+                COPY_WITH_PARITY_DIFFERENCE(F32);
+                COPY_WITH_PARITY_DIFFERENCE(U8);
+              default:
+                psAbort("Should never get here.\n");
+            }
+            continue;
+        }
+
+        // In the third case, the images are flipped and have different binnnig.
+        // We have to do all of the hard work ourselves
+        switch (type) {
+            FILL_IN(F32);
+            FILL_IN(U8);
+          default:
+            psAbort("Should never get here.\n");
+        }
+    } // Iterating over images
+
+    return mosaic;
+}
+
+// Add a cell and its various properties to the arrays
+static bool addCell(psArray *images,    // Array of images
+                    psArray *masks,     // Array of masks
+                    psArray *weights,   // Array of weights
+                    psVector *x0,       // Array of X0
+                    psVector *y0,       // Array of Y0
+                    psVector *xBin,     // Array of XBIN
+                    psVector *yBin,     // Array of YBIN
+                    psVector *xFlip,    // Array indicating whether x axis should be flipped
+                    psVector *yFlip,    // Array indicating whether y axis should be flipped
+                    const pmCell *cell, // Cell to add
+                    int *xBinMin,       // The minimum x binning, returned
+                    int *yBinMin,       // The minimum y binning, returned
+                    bool chipStuff,      // Worry about chip stuff as well?
+                    int x0Target, int y0Target, // Target x0 and y0 offsets
+                    int xParityTarget, int yParityTarget // Target parities
+                   )
+{
+    if (!cell) {
+        return false;
+    }
+
+    // Expand the arrays and vectors to handle new data
+    long index = images->n;               // The index to use
+    if (images->n == images->nalloc) {
+        images  = psArrayRealloc(images,  index + CELL_LIST_BUFFER);
+        masks   = psArrayRealloc(masks,   index + CELL_LIST_BUFFER);
+        weights = psArrayRealloc(weights, index + CELL_LIST_BUFFER);
+        x0    = psVectorRealloc(x0,    index+ CELL_LIST_BUFFER);
+        y0    = psVectorRealloc(y0,    index+ CELL_LIST_BUFFER);
+        xBin  = psVectorRealloc(xBin,  index+ CELL_LIST_BUFFER);
+        yBin  = psVectorRealloc(yBin,  index+ CELL_LIST_BUFFER);
+        xFlip = psVectorRealloc(xFlip, index+ CELL_LIST_BUFFER);
+        yFlip = psVectorRealloc(yFlip, index+ CELL_LIST_BUFFER);
+    }
+
+    images->n = index + 1;
+    masks->n = index + 1;
+    weights->n = index + 1;
+    x0->n = index + 1;
+    y0->n = index + 1;
+    xBin->n = index + 1;
+    yBin->n = index + 1;
+    xFlip->n = index + 1;
+    yFlip->n = index + 1;
+
+    bool mdok = true;                   // Status of MD lookup
+    bool good = true;                   // Is everything good?
+
+    // Offset of the cell on the chip
+    int x0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0");
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.X0 for cell is not set.\n");
+        good = false;
+    }
+    int y0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 for cell is not set.\n");
+        good = false;
+    }
+    psTrace("psModules.camera", 5, "Cell %ld: x0=%d y0=%d\n", index, x0Cell, y0Cell);
+
+    // Offset of the chip on the FPA
+    int x0Chip = 0, y0Chip = 0;
+    if (chipStuff) {
+        pmChip *chip = cell->parent;    // The parent chip
+        if (!chip) {
+            psError(PS_ERR_UNKNOWN, true, "Cell has no parent chip --- can't find CHIP.X0 and CHIP.Y0\n");
+            good = false;
+        }
+        x0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.X0 for chip is not set.\n");
+            good = false;
+        }
+        y0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.Y0 for chip is not set.\n");
+            good = false;
+        }
+    }
+
+    // Binning
+    xBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN");
+    if (!mdok || xBin->data.S32[index] == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XBIN for cell is not set.\n");
+        return false;
+    } else if (xBin->data.S32[index] < *xBinMin) {
+        *xBinMin = xBin->data.S32[index];
+    }
+    yBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN");
+    if (!mdok || yBin->data.S32[index] == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YBIN for cell is not set.\n");
+        return false;
+    } else if (yBin->data.S32[index] < *yBinMin) {
+        *yBinMin = yBin->data.S32[index];
+    }
+
+    // Do we need to flip?
+    int xParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityCell != 1 && xParityCell != -1)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY for cell is not set.\n");
+        return false;
+    }
+    int yParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityCell != 1 && yParityCell != -1)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY for cell is not set.\n");
+        return false;
+    }
+
+    // Parity of the chip on the FPA
+    int xParityChip = 1, yParityChip = 1;
+    if (chipStuff) {
+        pmChip *chip = cell->parent;    // The parent chip
+        xParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XPARITY");
+        if (!mdok || (xParityChip != 1 && xParityChip != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.XPARITY for chip is not set.\n");
+            return false;
+        }
+        yParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YPARITY");
+        if (!mdok || (yParityChip != 1 && yParityChip != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.YPARITY for chip is not set.\n");
+            return false;
+        }
+    }
+
+    // Set the flips on the basis of the parity
+    // XXX if (level == CHIP) : only apply Cell parity
+    // XXX if (level == FPA) : apply Chip & Cell parity
+    if (xParityCell * xParityChip == xParityTarget) {
+        xFlip->data.U8[index] = 0;
+    } else {
+        xFlip->data.U8[index] = 1;
+    }
+    if (yParityCell * yParityChip == yParityTarget) {
+        yFlip->data.U8[index] = 0;
+    } else {
+        yFlip->data.U8[index] = 1;
+    }
+
+    x0->data.S32[index] = x0Chip + x0Cell - x0Target;
+    y0->data.S32[index] = y0Chip + y0Cell - y0Target;
+
+    // Add the readout to the array of images to be mosaicked
+    psArray *readouts = cell->readouts; // The array of readouts
+    if (readouts->n > 1) {
+        psWarning("Cell contains more than one readout (%ld) --- only the first will be mosaicked.\n",
+                  readouts->n);
+    }
+    pmReadout *readout = readouts->data[0]; // The only readout we'll bother with
+
+    // The images to put into the mosaic
+    images->data[index]  = psMemIncrRefCounter(readout->image);
+    weights->data[index] = psMemIncrRefCounter(readout->weight);
+    masks->data[index]   = psMemIncrRefCounter(readout->mask);
+
+    psTrace("psModules.camera", 9, "Added cell (%p) %ld: %d,%d; %d,%d, %d,%d.\n", cell, index,
+            x0->data.S32[index], y0->data.S32[index], xBin->data.S32[index], yBin->data.S32[index],
+            xFlip->data.U8[index], yFlip->data.U8[index]);
+
+    return true;
+}
+
+
+// Mosaic together the cells in a chip
+static bool chipMosaic(psImage **mosaicImage, // The mosaic image, to be returned
+                       psImage **mosaicMask, // The mosaic mask, to be returned
+                       psImage **mosaicWeights, // The mosaic weights, to be returned
+                       int *xBinChip, int *yBinChip, // The binning in x and y, to be returned
+                       const pmChip *chip, // Chip to mosaic
+                       const pmCell *targetCell, // Cell to which to mosaic
+                       psMaskType blank // Mask value to give blank pixels
+                      )
+{
+    assert(mosaicImage);
+    assert(mosaicMask);
+    assert(mosaicWeights);
+    assert(xBinChip);
+    assert(yBinChip);
+    assert(chip);
+    assert(targetCell);
+
+    psArray *images = psArrayAlloc(0); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(0); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(0); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(0, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(0, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in y?
+
+    // Get the target characteristics
+    bool mdok = true;                   // Status of MD lookup
+    int x0Target = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.X0");
+    if (!mdok) {
+        psWarning("CELL.X0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.X0", S32, 0);
+    }
+    int y0Target = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psWarning("CELL.Y0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.Y0", S32, 0);
+    }
+    int xParityTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityTarget != -1 && xParityTarget != 1)) {
+        psWarning("CELL.XPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.XPARITY", S32, 1);
+        xParityTarget = 1;
+    }
+    int yParityTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityTarget != -1 && yParityTarget != 1)) {
+        psWarning("CELL.YPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.YPARITY", S32, 1);
+        yParityTarget = 1;
+    }
+
+    // Binning for the mosaicked chip is the minimum binning allowed by the cells
+    *xBinChip = INT_MAX;
+    *yBinChip = INT_MAX;
+
+    // Set up the required inputs
+    bool allGood = true;                // Is everything good, well-behaved?
+    psArray *cells = chip->cells;       // The array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        if (!cell || !cell->data_exists) {
+            continue;
+        }
+        allGood &= addCell(images, masks, weights, x0, y0, xBin, yBin, xFlip, yFlip,
+                           cell, xBinChip, yBinChip, false, x0Target, y0Target,
+                           xParityTarget, yParityTarget);
+    }
+
+    // Check to see if the target has a smaller binning in mind
+    int xBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XBIN");
+    if (!mdok || xBinTarget == 0) {
+        // CELL.XBIN is not set for the target cell --- assume it's the same as the source
+        FIX_CONCEPT(targetCell->concepts, "CELL.XBIN", S32, *xBinChip);
+    } else {
+        *xBinChip = xBinTarget;
+    }
+    int yBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YBIN");
+    if (!mdok || yBinTarget == 0) {
+        // CELL.YBIN is not set for the target cell --- assume it's the same as the source
+        FIX_CONCEPT(targetCell->concepts, "CELL.YBIN", S32, *yBinChip);
+    } else {
+        *yBinChip = yBinTarget;
+    }
+
+    // Mosaic the images together and we're done
+    if (allGood) {
+        *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, BLANK_VALUE);
+        *mosaicWeights = imageMosaic(weights, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, BLANK_VALUE);
+        *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, blank);
+    }
+
+    // Clean up
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(x0);
+    psFree(y0);
+
+    return allGood;
+}
+
+// Mosaic together the cells in a FPA
+static bool fpaMosaic(psImage **mosaicImage, // The mosaic image, to be returned
+                      psImage **mosaicMask, // The mosaic mask, to be returned
+                      psImage **mosaicWeights, // The mosaic weights, to be returned
+                      int *xBinFPA, int *yBinFPA, // The binning in x and y, to be returned
+                      const pmFPA *fpa,  // FPA to mosaic
+                      const pmChip *targetChip, // Chip to which to mosaic
+                      const pmCell *targetCell, // Cell to which to mosaic
+                      psMaskType blank  // Mask value to give blank pixels
+                     )
+{
+    assert(mosaicImage);
+    assert(mosaicMask);
+    assert(mosaicWeights);
+    assert(xBinFPA);
+    assert(yBinFPA);
+    assert(fpa);
+    assert(targetChip);
+    assert(targetCell);
+
+    psArray *images = psArrayAlloc(0); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(0); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(0); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(0, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(0, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in y?
+
+    // Get the target characteristics
+    bool mdok = true;                   // Status of MD lookup
+    int x0Target = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.X0");
+    if (!mdok) {
+        psWarning("CHIP.X0 is not set for the target chip; assuming 0.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.X0", S32, 0);
+    }
+    int y0Target = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.Y0");
+    if (!mdok) {
+        psWarning("CHIP.Y0 is not set for the target chip; assuming 0.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.Y0", S32, 0);
+    }
+    x0Target += psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.X0");
+    if (!mdok) {
+        psWarning("CELL.X0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.X0", S32, 0);
+    }
+    y0Target += psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psWarning("CELL.Y0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.Y0", S32, 0);
+    }
+    int xParityChipTarget = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.XPARITY");
+    if (!mdok || (xParityChipTarget != -1 && xParityChipTarget != 1)) {
+        psWarning("CHIP.XPARITY is not set for the target chip; assuming 1.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.XPARITY", S32, 1);
+        xParityChipTarget = 1;
+    }
+    int yParityChipTarget = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.YPARITY");
+    if (!mdok || (yParityChipTarget != -1 && yParityChipTarget != 1)) {
+        psWarning("CHIP.YPARITY is not set for the target chip; assuming 1.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.YPARITY", S32, 1);
+        yParityChipTarget = 1;
+    }
+    int xParityCellTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityCellTarget != -1 && xParityCellTarget != 1)) {
+        psWarning("CELL.XPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.XPARITY", S32, 1);
+        xParityCellTarget = 1;
+    }
+    int yParityCellTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityCellTarget != -1 && yParityCellTarget != 1)) {
+        psWarning("CELL.YPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.YPARITY", S32, 1);
+        yParityCellTarget = 1;
+    }
+    int xParityTarget = xParityChipTarget * xParityCellTarget;
+    int yParityTarget = yParityChipTarget * yParityCellTarget;
+
+    // Binning for the mosaicked chip is the minimum binning allowed by the cells
+    *xBinFPA = INT_MAX;
+    *yBinFPA = INT_MAX;
+
+    // Set up the required inputs
+    bool allGood = true;                // Is everything good, well-behaved?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The chip of interest
+        if (!chip || !chip->data_exists) {
+            continue;
+        }
+        psArray *cells = chip->cells;   // The array of cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j];  // The cell of interest
+            if (!cell || !cell->data_exists) {
+                continue;
+            }
+            allGood |= addCell(images, masks, weights, x0, y0, xBin, yBin, xFlip, yFlip,
+                               cell, xBinFPA, yBinFPA, true, x0Target, y0Target,
+                               xParityTarget, yParityTarget);
+        }
+    }
+
+    // Check to see if the target has a smaller binning in mind
+    int xBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XBIN");
+    if (mdok && xBinTarget != 0) {
+        *xBinFPA = xBinTarget;
+    }
+    int yBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YBIN");
+    if (mdok && yBinTarget != 0) {
+        *yBinFPA = yBinTarget;
+    }
+
+    // Mosaic the images together and we're done
+    if (allGood) {
+        *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, BLANK_VALUE);
+        *mosaicWeights = imageMosaic(weights, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, BLANK_VALUE);
+        *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, blank);
+    }
+
+    // Clean up
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(x0);
+    psFree(y0);
+
+    return allGood;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Mosaic all the cells in a chip together.
+//
+// It is desirable to do this without using psImageOverlay (or similar) if it can be at all avoided (because
+// it's really really slow in that case).  There are therefore two cases:
+//
+// 1. The HDU is at the Chip or FPA level.  This is the fast case, and only works if the HDU is "nice", by
+// which I mean:
+//
+//    - the CELL.TRIMSECs are contiguous on the HDU image
+//    - the CELL.PARITYs are identically +1
+//    - the CELL.XBIN and CELL.YBIN are all identical
+//
+// Then we can just use psImageSubset to get the "mosaicked" chip.
+//
+//
+// 2. The HDU is at the cell level, or the above requirements are not met, in which case we mosaic the cells.
+// This is the slow case.  We need to:
+//
+//    - Throw away the bias regions
+//    - Convert all cells to common parity
+//    - Mosaic the cells into an HDU image using CELL.X0 and CELL.Y0
+//    - Update CELL.TRIMSECs
+//
+// Once the demands of case 1 have been met, or case 2 has been performed, then we can create a cell to hold
+// the mosaic image.
+
+bool pmChipMosaic(pmChip *target, const pmChip *source, bool deepCopy, psMaskType blank)
+{
+    // Target exists, and has only a single cell
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(target->cells, false);
+    if (target->cells->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target chip for mosaicking must contain a single cell.\n");
+        return false;
+    }
+    pmCell *targetCell = target->cells->data[0]; // The target cell
+    PS_ASSERT_PTR_NON_NULL(targetCell, false);
+    // Source exists
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+
+    psImage *mosaicImage   = NULL;      // The mosaic image
+    psImage *mosaicMask    = NULL;      // The mosaic mask
+    psImage *mosaicWeights = NULL;      // The mosaic weights
+
+    // Find the HDU
+    psRegion *chipRegion = NULL;        // Region on the HDU that corresponds to the chip
+    int xBin = 0, yBin = 0;             // Binning for the chip mosaic
+    if (!deepCopy && (chipRegion = niceChip(&xBin, &yBin, source))) {
+        // Case 1 --- we need only cut out the region
+        psTrace("psModules.camera", 1, "Case 1 mosaicking: simple cut-out.\n");
+        pmHDU *hdu = source->hdu;       // The HDU that has the pixels
+        if (!hdu || !hdu->images) {
+            hdu = source->parent->hdu;
+        }
+        // force limits to land on chip
+        psRegion bounds = psRegionForImage (hdu->images->data[0], *chipRegion);
+        mosaicImage = psImageSubset(hdu->images->data[0], bounds);
+        if (!mosaicImage) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to select image pixels.\n");
+            return false;
+        }
+        if (hdu->masks) {
+            mosaicMask = psImageSubset(hdu->masks->data[0], bounds);
+            if (!mosaicMask) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to select mask pixels.\n");
+                return false;
+            }
+        }
+        if (hdu->weights) {
+            mosaicWeights = psImageSubset(hdu->weights->data[0], bounds);
+            if (!mosaicWeights) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to select weight pixels.\n");
+                return false;
+            }
+        }
+    } else {
+        // Case 2 --- we need to mosaic by cut and paste
+        psTrace("psModules.camera", 1, "Case 2 mosaicking: cut and paste.\n");
+        if (!chipMosaic(&mosaicImage, &mosaicMask, &mosaicWeights, &xBin, &yBin, source, targetCell, blank)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mosaic cells.\n");
+            return false;
+        }
+        chipRegion = psRegionAlloc(0, 0, 0, 0); // We've cut and paste, so there's no valid trimsec
+        *chipRegion = psRegionForImage (mosaicImage, *chipRegion);
+    }
+    psTrace("psModules.camera", 1, "xBin,yBin: %d,%d\n", xBin, yBin);
+
+    // Set the concepts for the target cell
+    psList *sourceCells = psArrayToList(source->cells); // List of cells
+    pmConceptsAverageCells(targetCell, sourceCells, chipRegion, NULL, false);
+    {
+        psMetadataItem *item = psMetadataLookup(targetCell->concepts, "CELL.X0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.Y0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.XBIN");
+        item->data.S32 = xBin;
+        item = psMetadataLookup(targetCell->concepts, "CELL.YBIN");
+        item->data.S32 = yBin;
+    }
+    psFree(sourceCells);
+    psFree(chipRegion);
+
+    // Copy the concepts
+    target->concepts = psMetadataCopy(target->concepts, source->concepts); // Chip level
+    target->parent->concepts = psMetadataCopy(target->parent->concepts, source->parent->concepts); // FPA lvl
+
+    // Now make a new readout to go in the target cell
+    pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
+    newReadout->image  = mosaicImage;
+    newReadout->mask   = mosaicMask;
+    newReadout->weight = mosaicWeights;
+    psFree(newReadout);                 // Drop reference
+
+    // Data now exists in the targets
+    pmChipSetDataStatus(target, true);
+    pmCellSetDataStatus(targetCell, true);
+    newReadout->data_exists = true;
+
+    // Update the headers
+    pmHDU *sourceHDU = pmHDUFromChip(source); // The HDU for the source
+    pmHDU *targetHDU = pmHDUFromChip(target); // The HDU for the target
+    targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+    pmHDU *targetPHU = pmHDUGetHighest(target->parent, target, NULL);
+    pmHDU *sourcePHU = pmHDUGetHighest(source->parent, source, NULL);
+
+    // Need to update NAXIS1, NAXIS2 in the target header, so that when we write a CMF, it has the correct
+    // extent.  I'm not convinced that this is the best way to do this, but it should be, at worst, harmless,
+    // since NAXIS[12] will get overwritten for an image with the proper dimensions.
+    psRegion *naxis = pmChipExtent(target);
+    psMetadataAddS32(targetHDU->header, PS_LIST_TAIL, "NAXIS1", PS_META_REPLACE, "Size in x",
+                     naxis->x1 - naxis->x0);
+    psMetadataAddS32(targetHDU->header, PS_LIST_TAIL, "NAXIS2", PS_META_REPLACE, "Size in y",
+                     naxis->y1 - naxis->y0);
+    psFree(naxis);
+
+
+    if (!targetPHU) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find HDU after mosaicking.\n");
+        return false;
+    }
+    if (!targetPHU->header) {
+        // if we don't yet have a header, copy this one.
+        // XXX do we need to create an empty one if the levels do not match??
+        if (true) {
+            targetPHU->header = psMetadataCopy(targetPHU->header, sourcePHU->header);
+        } else {
+            targetPHU->header = psMetadataAlloc();
+        }
+    }
+
+    if (!pmConfigConformHeader(targetPHU->header, targetPHU->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header after mosaicking.\n");
+        return false;
+    }
+
+    // If the cells contain the headers, we need to apply the WCS terms from (one of?) the cells
+    int xParityCellTarget = psMetadataLookupS32(NULL, targetCell->concepts, "CELL.XPARITY");
+    if (xParityCellTarget != -1 && xParityCellTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.XPARITY is not set for target.");
+        return false;
+    }
+    int xParityChipTarget = psMetadataLookupS32(NULL, target->concepts, "CHIP.XPARITY");
+    if (xParityChipTarget != -1 && xParityChipTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.XPARITY is not set for target.");
+        return false;
+    }
+    int xParityTarget = xParityCellTarget * xParityChipTarget; // Target parity in x
+
+    int yParityCellTarget = psMetadataLookupS32(NULL, targetCell->concepts, "CELL.YPARITY");
+    if (yParityCellTarget != -1 && yParityCellTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.YPARITY is not set for target.");
+        return false;
+    }
+    int yParityChipTarget = psMetadataLookupS32(NULL, target->concepts, "CHIP.YPARITY");
+    if (yParityChipTarget != -1 && yParityChipTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.YPARITY is not set for target.");
+        return false;
+    }
+    int yParityTarget = yParityCellTarget * yParityChipTarget; // Target parity in y
+
+    for (int i = 0; i < source->cells->n; i++) {
+        pmCell *cell = source->cells->data[i];
+        if (!cell || !cell->hdu || !cell->hdu->header) {
+            continue;
+        }
+
+        pmAstromWCS *wcs = pmAstromWCSfromHeader(cell->hdu->header); // WCS terms for this cell
+        if (!wcs) {
+            psTrace("psModules.camera", 1, "Unable to read cell WCS to generate chip WCS --- ignored.");
+            continue;
+        }
+
+        int xBinCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Cell binning in x
+        if (xBinCell == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.XBIN is not set.");
+            return false;
+        }
+        int xParitySource = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY") *
+            psMetadataLookupS32(NULL, source->concepts, "CHIP.XPARITY"); // Source parity in x
+        if (xParitySource != -1 && xParitySource != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.XPARITY or CELL.XPARITY is not set for source.");
+            return false;
+        }
+        bool xFlip = (xParitySource == xParityTarget ? false : true); // Flip the x sense of the WCS?
+        int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0"); // Cell offset in x
+
+        // Modify the wcs terms for the cell offset, binning, and parity
+        float xBinRatio = (float)xBinCell / (float)xBin;
+        if (xFlip) {
+            wcs->crpix1 = x0Cell - wcs->crpix1 * xBinRatio;
+            wcs->cdelt1 *= -1;
+        } else {
+            wcs->crpix1 = x0Cell + wcs->crpix1 * xBinRatio;
+        }
+        wcs->cdelt1 *= xBinRatio;
+
+        int yBinCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Cell binning in y
+        if (yBinCell == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.YBIN is not set.");
+            return false;
+        }
+        int yParitySource = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY") *
+            psMetadataLookupS32(NULL, cell->parent->concepts, "CHIP.YPARITY"); // Source parity in y
+        if (yParitySource != -1 && yParitySource != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.YPARITY or CELL.YPARITY is not set for source.");
+            return false;
+        }
+        bool yFlip = (yParitySource == yParityTarget ? false : true); // Flip the y sense of the WCS?
+        int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0"); // Cell offset in y
+
+        float yBinRatio = (float)yBinCell / (float)yBin;
+        if (yFlip) {
+            wcs->crpix2 = y0Cell - wcs->crpix2 * yBinRatio;
+            wcs->cdelt2 *= -1;
+        } else {
+            wcs->crpix2 = y0Cell + wcs->crpix2 * yBinRatio;
+        }
+        wcs->cdelt2 *= yBinRatio;
+
+        if (!pmAstromWCStoHeader(targetHDU->header, wcs)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate chip WCS from cell WCS.");
+            psFree(wcs);
+            return false;
+        }
+        psFree(wcs);
+
+        // XXX rather than quitting at this point, we could save this wcs structure and compare
+        // its values to the equivalent version from one of the other cells.
+        break;
+    }
+
+    return true;
+}
+
+
+bool pmFPAMosaic(pmFPA *target, const pmFPA *source, bool deepCopy, psMaskType blank)
+{
+    // Target exists, and has only a single chip with single cell
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(target->chips, false);
+    if (target->chips->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target FPA for mosaicking must contain a single chip.\n");
+        return false;
+    }
+    pmChip *targetChip = target->chips->data[0]; // The target chip
+    PS_ASSERT_PTR_NON_NULL(targetChip, false);
+    PS_ASSERT_PTR_NON_NULL(targetChip->cells, false);
+    if (target->chips->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target FPA for mosaicking must contain a single cell.\n");
+        return false;
+    }
+    pmCell *targetCell = targetChip->cells->data[0]; // The target cell
+    PS_ASSERT_PTR_NON_NULL(targetCell, false);
+    // Source exists
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    psImage *mosaicImage   = NULL;      // The mosaic image
+    psImage *mosaicMask    = NULL;      // The mosaic mask
+    psImage *mosaicWeights = NULL;      // The mosaic weights
+
+    // Find the HDU
+    psRegion *fpaRegion = NULL;         // Region on the HDU that corresponds to the FPA
+    int xBin = 0, yBin = 0;             // Binning for the FPA mosaic
+    if (!deepCopy && (fpaRegion = niceFPA(&xBin, &yBin, source))) {
+        // Case 1 --- we need only cut out the region
+        psTrace("psModules.camera", 1, "Case 1 mosaicking: simple cut-out.\n");
+        pmHDU *hdu = source->hdu;         // The HDU that has the pixels
+        mosaicImage = psImageSubset(hdu->images->data[0], *fpaRegion);
+        if (hdu->masks) {
+            mosaicMask = psImageSubset(hdu->masks->data[0], *fpaRegion);
+        }
+        if (hdu->weights) {
+            mosaicWeights = psImageSubset(hdu->weights->data[0], *fpaRegion);
+        }
+    } else {
+        // Case 2 --- we need to mosaic by cut and paste
+        psTrace("psModules.camera", 1, "Case 2 mosaicking: cut and paste.\n");
+        if (!fpaMosaic(&mosaicImage, &mosaicMask, &mosaicWeights, &xBin, &yBin, source,
+                       targetChip, targetCell, blank)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mosaic chips.\n");
+            return false;
+        }
+        fpaRegion = psRegionAlloc(NAN, NAN, NAN, NAN); // We've cut and paste, so there's no valid trimsec
+    }
+
+    // Set the concepts for the target cell, and add the mosaic in
+    // First we need a list of cells
+    psList *sourceCells = psListAlloc(NULL); // List of source cells
+    psArray *chips = source->chips;        // Array of chips
+    pmChip *firstSourceChip = NULL;     // The first chip in the source FPA; for headers
+    pmCell *firstSourceCell = NULL;     // The first cell in the source FPA; for headers
+    for (long i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chip || !chip->data_exists) {
+            continue;
+        }
+        psArray *cells = chip->cells;
+        for (long j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // Cell of interest
+            if (!cell || !cell->data_exists) {
+                continue;
+            }
+            psListAdd(sourceCells, PS_LIST_TAIL, cell);
+
+            // These are valid chip and cell to use for the header; grab the first such
+            if (!firstSourceCell && !firstSourceChip) {
+                firstSourceCell = cell;
+                firstSourceChip = chip;
+            }
+        }
+    }
+    pmConceptsAverageCells(targetCell, sourceCells, fpaRegion, NULL, false);
+    {
+        psMetadataItem *item = psMetadataLookup(targetCell->concepts, "CELL.X0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.Y0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.XBIN");
+        item->data.S32 = xBin;
+        item = psMetadataLookup(targetCell->concepts, "CELL.YBIN");
+        item->data.S32 = yBin;
+    }
+    psFree(sourceCells);
+    psFree(fpaRegion);
+
+    // Currently, there's nothing interesting in the chip concepts that needs to be updated.
+
+    // Copy the concepts for the target FPA
+    target->concepts = psMetadataCopy(target->concepts, source->concepts);
+
+    // Now make a new readout to go in the new cell
+    pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
+    newReadout->image  = mosaicImage;
+    newReadout->mask   = mosaicMask;
+    newReadout->weight = mosaicWeights;
+    psFree(newReadout);                 // Drop reference
+
+    // Data now exists in the targets
+    pmChipSetDataStatus(targetChip, true);
+    pmCellSetDataStatus(targetCell, true);
+    newReadout->data_exists = true;
+
+    // Update the headers
+    pmHDU *sourceHDU = pmHDUGetHighest(source, firstSourceChip, firstSourceCell); // The HDU for the source
+    if (!sourceHDU) {
+        psWarning("Unable to find HDU in source FPA; unable to copy headers.\n");
+        return false;
+    }
+    pmHDU *targetHDU = pmHDUGetHighest(target, targetChip, targetCell); // The HDU for the target
+    if (!targetHDU) {
+        psWarning("Unable to find HDU in target FPA; unable to copy headers.\n");
+        return false;
+    }
+
+    if (sourceHDU->header) {
+        targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+    } else if (!targetHDU->header) {
+        targetHDU->header = psMetadataAlloc();
+    }
+
+    if (!pmConfigConformHeader(targetHDU->header, targetHDU->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header after mosaicking.\n");
+        return false;
+    }
+
+    return true;
+}
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMosaic.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMosaic.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAMosaic.h	(revision 22290)
@@ -0,0 +1,41 @@
+/* @file pmFPAMosaic.h
+ * @brief Functions to mosaic FPA components into a single entity
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-20 02:22:26 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CHIP_MOSAIC_H
+#define PM_CHIP_MOSAIC_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Mosaic all cells within a chip
+///
+/// Mosaics all cells within the source into a single cell within the target (which must have only a single
+/// cell).  Cells are placed on the chip according to the CELL.X0 and CELL.Y0 offsets.  This is useful for
+/// getting an image of the chip on the sky.  The mosaicking is done so as to avoid performing a deep copy of
+/// the pixels, if possible.
+bool pmChipMosaic(pmChip *target,       ///< Target chip --- may contain only a single cell
+                  const pmChip *source, ///< Source chip whose cells will be mosaicked
+                  bool deepCopy,        ///< Require a deep copy (disregard 'nice' chip)
+                  psMaskType blank      ///< Mask value to give blank pixels
+    );
+
+/// Mosaic all cells within an FPA
+///
+/// Mosaics all cells within the source into a single chip with single cell within the target (which must have
+/// only a single chip with single cell).  Cells are placed on the FPA according to the CHIP.X0, CHIP.Y0,
+/// CELL.X0 and CELL.Y0 offsets.  This is useful for getting an image of the FPA on the sky.  The mosaicking
+/// is done so as to avoid performing a deep copy of the pixels, if possible.
+bool pmFPAMosaic(pmFPA *target, ///< Target FPA --- may contain only a single chip with a single cell
+                 const pmFPA *source,   ///< FPA whose chips and cells will be mosaicked
+                 bool deepCopy,         ///< Require a deep copy (disregard 'nice' chip)
+                 psMaskType blank       ///< Mask value to give blank pixels
+                );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPARead.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPARead.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPARead.c	(revision 22290)
@@ -0,0 +1,766 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+#include "pmFPARead.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Definitions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Specify what to read
+typedef enum {
+    FPA_READ_TYPE_IMAGE,                // Read image
+    FPA_READ_TYPE_MASK,                 // Read mask
+    FPA_READ_TYPE_WEIGHT,               // Read weight map
+    FPA_READ_TYPE_HEADER                // Read header
+} fpaReadType;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Carve a readout from the image pixels
+static bool readoutCarve(pmReadout *readout, // Readout to be carved up
+                         psImage *image, // Image that will be carved
+                         const psRegion *trimsec, // Trim section
+                         const psList *biassecs, // Bias sections
+                         fpaReadType type
+                        )
+{
+    assert(readout);
+    assert(image);
+    assert(trimsec);
+    assert(biassecs);
+
+    // The image corresponding to the trim region
+    if (psRegionIsNaN(*trimsec)) {
+        psString regionString = psRegionToString(*trimsec);
+        psError(PS_ERR_UNKNOWN, true, "Invalid trim section: %s\n", regionString);
+        psFree(regionString);
+        psFree(readout);
+        return false;
+    }
+    psRegion region = psRegionSet(PS_MAX(trimsec->x0 - readout->col0, 0), // x0
+                                  PS_MIN(trimsec->x1 - readout->col0, image->numCols), // x1
+                                  PS_MAX(trimsec->y0 - readout->row0, 0), // y0
+                                  PS_MIN(trimsec->y1 - readout->row0, image->numRows) // y1
+                                 );
+
+    // place the image subset in the appropriate target location, freeing if needed
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        if (readout->image) {
+            psFree (readout->image);
+        }
+        readout->image = psImageSubset(image, region);
+        break;
+      case FPA_READ_TYPE_MASK:
+        if (readout->mask) {
+            psFree (readout->mask);
+        }
+        readout->mask = psImageSubset(image, region);
+        break;
+      case FPA_READ_TYPE_WEIGHT:
+        if (readout->weight) {
+            psFree (readout->weight);
+        }
+        readout->weight = psImageSubset(image, region);
+        break;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+
+    // Get the list of overscans
+    // XXX should this step only be performed for IMAGE, not MASK and WEIGHT types?
+    // XXX that would allow us to overlay a MASK and WEIGHT which have been trimmed...
+    if (readout->bias->n != 0) {
+        // Make way!
+        psFree(readout->bias);
+        readout->bias = psListAlloc(NULL);
+    }
+    psListIterator *iter = psListIteratorAlloc((psList*)biassecs, PS_LIST_HEAD, false); // Iterator
+    psRegion *biassec = NULL;       // A BIASSEC region from the list
+    while ((biassec = psListGetAndIncrement(iter))) {
+        if (psRegionIsNaN(*biassec)) {
+            psString regionString = psRegionToString(*biassec);
+            psError(PS_ERR_IO, true, "Invalid bias section: %s\n", regionString);
+            psFree(regionString);
+            psFree(readout);
+            return false;
+        }
+        psRegion region = psRegionSet(PS_MAX(biassec->x0 - readout->col0, 0), // x0
+                                      PS_MIN(biassec->x1 - readout->col0, image->numCols), // x1
+                                      PS_MAX(biassec->y0 - readout->row0, 0), // y0
+                                      PS_MIN(biassec->y1 - readout->row0, image->numRows) // y1
+                                     );
+        psImage *overscan = psImageSubset(image, region);
+        psListAdd(readout->bias, PS_LIST_TAIL, overscan);
+        psFree(overscan);
+    }
+    psFree(iter);
+
+    return true;
+}
+
+// Read a component of a readout.  We read in only the rows from min to max for plane z, for
+// the full region requested.  if we request a range outside the region, we will pad to fill
+// out the edges of the region with 'bad' pixels.  The output image always has max-min rows.
+// The region represents the maximum bounds of the full image
+
+static psImage *readoutReadComponent(psImage *image, // Image into which to read
+                                     psFits *fits, // FITS file from which to read
+                                     const psRegion *fullImage, // full image region, read a subset
+                                     int readdir, // Read direction (1=rows, 2=cols)
+                                     int min,  // Minimum row/col number to read
+                                     int max,   // Maximum row/col number to read
+                                     int z,     // Image plane to read
+                                     float bad // Bad value
+    )
+{
+    assert(fits);
+    assert(fullImage);
+    assert((readdir == 1) || (readdir == 2));
+
+    int nRead = 0;
+    int nScans = max - min;
+    assert (nScans > 0);
+
+    psRegion toRead = *fullImage;  // full image region
+
+    int dX = 0;
+    int dY = 0;
+    int nX = 0;
+    int nY = 0;
+
+    if (readdir == 1) {
+        toRead.y0 = PS_MAX (toRead.y0, min);
+        toRead.y1 = PS_MIN (toRead.y1, max);
+        nRead = toRead.y1 - toRead.y0;
+        if (min < fullImage->y0) {
+            dY = toRead.y0;
+        }
+        nX = toRead.x1 - toRead.x0;
+        nY = nScans;
+    } else {
+        toRead.x0 = PS_MAX (toRead.x0, min);
+        toRead.x1 = PS_MIN (toRead.x1, max);
+        nRead = toRead.x1 - toRead.x0;
+        if (min < fullImage->x0) {
+            dX = toRead.x0;
+        }
+        nX = nScans;
+        nY = toRead.y1 - toRead.y0;
+    }
+
+    psTrace("psModules.camera", 5, "Reading section [%.0f:%.0f,%.0f:%.0f]\n",
+            toRead.x0, toRead.x1, toRead.y0, toRead.y1);
+    image = psFitsReadImageBuffer(image, fits, toRead, z); // Desired pixels
+    psTrace("psModules.camera", 7, "Image is %dx%d\n", image->numCols, image->numRows);
+
+    // XXX: We only support F32 for now
+    if (image->type.type != PS_TYPE_F32) {
+        psImage *temp = psImageCopy(NULL, image, PS_TYPE_F32);
+        psFree(image);
+        image = temp;
+    }
+
+    // XXX this modification is not carried back up stream: it affects readout->row0,col0
+    if (nRead < nScans) {
+        // The region of interest is smaller than the number of pixels we want.
+        psTrace("psModules.camera", 5, "Resizing image to %d,%d\n", nX, nY);
+        psImage *temp = psImageAlloc(nX, nY, image->type.type);
+        psImageInit(temp, bad);
+        psImageOverlaySection(temp, image, dX, dY, "=");
+        psFree(image);
+        image = temp;
+    }
+
+    return image;
+}
+
+
+// Read into an cell; this is the engine for pmCellRead, pmCellReadMask, pmCellReadWeight
+// Does most of the work for the reading --- reads the HDU, and portions the HDU into readouts.
+static bool cellRead(pmCell *cell,      // Cell into which to read
+                     psFits *fits,      // FITS file from which to read
+                     psDB *db,          // Database handle, for concepts ingest
+                     fpaReadType type   // Type to read
+                    )
+{
+    assert(cell);
+    assert(fits);
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu) {
+        return true;                    // We read everything we could
+    }
+
+    // check if we have read the desired data, read it if needed
+    bool (*hduReadFunc)(pmHDU*, psFits*) = NULL; // Function to use to read the HDU
+    void *dataPointer = NULL;           // pointer to location of desired data
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        hduReadFunc = pmHDURead;
+        dataPointer = hdu->images;
+        break;
+      case FPA_READ_TYPE_HEADER:
+        hduReadFunc = pmHDUReadHeader;
+        dataPointer = hdu->header;
+        break;
+      case FPA_READ_TYPE_MASK:
+        hduReadFunc = pmHDUReadMask;
+        dataPointer = hdu->masks;
+        break;
+      case FPA_READ_TYPE_WEIGHT:
+        hduReadFunc = pmHDUReadWeight;
+        dataPointer = hdu->weights;
+        break;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+
+    // do we have the data we want (image, header, or etc).
+    if (!dataPointer) {
+        // attempt to read in the desired data
+        if (!hduReadFunc(hdu, fits)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
+            return false;
+        }
+    }
+
+    // load in the concept information for this cell
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        //psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+        //return false;
+        psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+    }
+
+    // skip the image arrays completely for the header-only files
+    if (type == FPA_READ_TYPE_HEADER) {
+        pmCellSetDataStatus(cell, true);
+        return true;
+    }
+
+    // set up pointers for the different possible image arrays
+    psArray *imageArray = NULL; // Array of images in the HDU
+    psElemType imageType = PS_TYPE_F32; // Expected type for image
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        imageArray = hdu->images;
+        imageType = PS_TYPE_F32;
+        break;
+      case FPA_READ_TYPE_MASK:
+        imageArray = hdu->masks;
+        imageType = PS_TYPE_MASK;
+        break;
+      case FPA_READ_TYPE_WEIGHT:
+        imageArray = hdu->weights;
+        imageType = PS_TYPE_F32;
+        break;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+
+    // Having read the cell, we now have to cut it up
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+    psList *biassecs = psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC");
+    if (psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, false, "CELL.TRIMSEC is not set --- can't read cell.\n");
+        return false;
+    }
+
+    // Iterate over each of the image planes, converting type if necessary, and extracting the bits that
+    // matter (CELL.TRIMSEC, CELL.BIASSEC) into readouts with readoutCarve.
+    for (int i = 0; i < imageArray->n; i++) {
+        psImage *source = imageArray->data[i]; // Source image, from the i-th plane
+
+        // Type conversion here to support the modules, which don't have multiple type support yet
+        if (source->type.type != imageType) {
+            psImage *temp = psImageCopy(NULL, source, imageType); // Temporary image
+            psFree(imageArray->data[i]);
+            imageArray->data[i] = temp;
+            source = temp;
+        }
+
+        pmReadout *readout;             // Readout into which to read
+        if (cell->readouts->n > i && cell->readouts->data[i]) {
+            readout = psMemIncrRefCounter(cell->readouts->data[i]);
+        } else {
+            readout = pmReadoutAlloc(cell);
+        }
+
+        if (!readoutCarve(readout, source, trimsec, biassecs, type)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to carve readout into image and bias sections for %d the plane.", i);
+            return NULL;
+        }
+        psFree(readout);                // Drop reference
+    }
+
+    pmCellSetDataStatus(cell, true);
+    return true;
+}
+
+
+// Read into an chip; this is the engine for pmChipRead, pmChipReadMask, pmChipReadWeight
+// Iterates over component cells, reading each
+static bool chipRead(pmChip *chip,      // Chip into which to read
+                     psFits *fits,      // FITS file from which to read
+                     psDB *db,          // Database handle, for concepts ingest
+                     fpaReadType type   // Type to read
+                    )
+{
+    assert(chip);
+    assert(fits);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        success |= cellRead(cell, fits, db, type);
+    }
+    if (success) {
+        if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for chip.\n");
+            return false;
+        }
+        // XXX probably could just use chip->data_exists
+        pmChipSetDataStatus(chip, true);
+    }
+
+    return success;
+}
+
+
+// Read into an FPA; this is the engine for pmFPARead, pmFPAReadMask, pmFPAReadWeight
+// Iterates over component chips, reading each
+static bool fpaRead(pmFPA *fpa,         // FPA into which to read
+                    psFits *fits,       // FITS file from which to read
+                    psDB *db,           // Database handle, for concepts ingest
+                    fpaReadType type    // Type to read
+                   )
+{
+    assert(fpa);
+    assert(fits);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The cell of interest
+        success |= chipRead(chip, fits, db, type);
+    }
+    if (success) {
+        if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read any chips in FPA");
+    }
+
+    return success;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading images
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// XXX need to pass back the error conditions as well as the 'keep going' state
+bool pmReadoutReadNext(bool *status, pmReadout *readout, psFits *fits, int z, int numScans)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    assert (numScans || !readout->image); // cannot have readout->image and !numScans
+
+    *status = false;
+
+    // Get the HDU and read the header
+    pmCell *cell = readout->parent;     // The parent cell
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu || hdu->blankPHU) {
+        // XXX is this an error condition?
+        *status = true;
+        return false;
+    }
+
+    if (!pmCellReadHeader(cell, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell!\n");
+        return false;
+    }
+
+    // Make sure we have the information we need
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                            PM_CONCEPT_SOURCE_DEFAULTS, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for cell.\n");
+        return false;
+    }
+
+    // Get the trim and bias sections
+    bool mdok = true;                   // Status of MD lookup
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+    psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!mdok || !biassecs) {
+        psError(PS_ERR_IO, true, "CELL.BIASSEC is not set.\n");
+        return false;
+    }
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.BAD is not set --- assuming zero.\n");
+        bad = 0.0;
+    }
+
+    // Check the third dimension
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read, as for a PHU.
+        *status = true;
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+    int naxis3 = 1;                     // The number of image planes
+    if (naxis == 3) {
+        naxis3 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS3");
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS3 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    if (z >= naxis3) {
+        // Nothing to see here.  Move along.
+        *status = true;
+        return false;
+    }
+
+    // Get the size of the image plane
+    int naxis1 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS1"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS1 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    int naxis2 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS2"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS2 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+
+    // rationalize trimsec against naxis1, naxis2
+    // valid range for trimsec is 1-Nx,1-Ny
+    // if (trimsec->x0 == 0) trimsec->x0 = 1;
+    if (trimsec->x1 <  1)
+        trimsec->x1 = naxis1 + trimsec->x1;
+    // if (trimsec->y0 == 0) trimsec->y0 = 1;
+    if (trimsec->y1 <  1)
+        trimsec->y1 = naxis2 + trimsec->y1;
+
+    int maxSize;                        // Number of cols,rows in image
+    if (readdir == 1) {
+        maxSize = PS_MIN(naxis2, trimsec->y1 - trimsec->y0);
+    } else {
+        maxSize = PS_MIN(naxis1, trimsec->x1 - trimsec->x0);
+    }
+
+    int offset;                         // start of the segment
+    int upper;                          // end of the segment
+    int lastScan;                       // last possible scan
+
+    // Calculate the segment offset and upper limit, adjust readout->row0,col0
+    if (readdir == 1) {
+        // Reading rows
+        offset = (readout->image) ? readout->row0 + numScans : 0; // extend to next section or start at beginning?
+        offset = (numScans == 0) ? trimsec->x0 : offset; // full array ? read full trimsec : read section
+        readout->row0 = offset;
+        readout->col0 = trimsec->x0;
+        lastScan = trimsec->y1;
+    } else {
+        // Reading cols
+        offset = (readout->image) ? readout->col0 + numScans : 0;
+        offset = (numScans == 0) ? trimsec->y0 : offset; // full array ? read full trimsec : read section
+        readout->col0 = offset;
+        readout->row0 = trimsec->y0;
+        lastScan = trimsec->x1;
+    }
+    upper = offset + numScans;
+
+    // Blow away existing data.
+    // Do this before returning, so that we're not returning data from a previous read
+    psFree(readout->image);
+    readout->image = NULL;
+
+    while (readout->bias->n > 0) {
+        psListRemove(readout->bias, PS_LIST_HEAD);
+    }
+
+    if (offset >= lastScan) {
+        // We've read everything there is
+        psTrace("psModules.camera", 7, "Read everything.\n");
+        *status = true;
+        return false;
+    }
+
+    psTrace("psModules.camera", 7, "offset=%d, upper = %d, image is %dx%d, trimsec [%.0f:%.0f,%.0f:%.0f]\n",
+            offset, upper, naxis1, naxis2, trimsec->x0, trimsec->x1, trimsec->y0, trimsec->y1);
+
+    // Get the new the trim section
+    readout->image = readoutReadComponent(readout->image, fits, trimsec, readdir, offset, upper, z, bad); // The image
+
+    // Get the new bias sections
+    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator for BIASSEC
+    psRegion *biassec = NULL;           // Bias section from iteration
+    while ((biassec = psListGetAndIncrement(biassecsIter))) {
+        psImage *bias = readoutReadComponent(NULL, fits, biassec, readdir, offset, upper, z, bad); // The bias
+        psListAdd(readout->bias, PS_LIST_TAIL, bias);
+        psFree(bias);                   // Drop reference
+    }
+    psFree(biassecsIter);
+
+    *status = true;
+    return true;
+}
+
+
+bool pmCellRead(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmChipRead(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmFPARead(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_IMAGE);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the mask
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadMask(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_MASK);
+}
+
+bool pmChipReadMask(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_MASK);
+}
+
+bool pmFPAReadMask(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_MASK);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the weight map
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadWeight(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmChipReadWeight(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmFPAReadWeight(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_WEIGHT);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the image header
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadHeaderSet(pmCell *cell, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, db, FPA_READ_TYPE_HEADER);
+}
+
+bool pmChipReadHeaderSet(pmChip *chip, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, db, FPA_READ_TYPE_HEADER);
+}
+
+bool pmFPAReadHeaderSet(pmFPA *fpa, psFits *fits, psDB *db)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, db, FPA_READ_TYPE_HEADER);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading FITS tables
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+int pmCellReadTable(pmCell *cell, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    const char *chipName = psMetadataLookupStr(NULL, cell->parent->concepts, "CHIP.NAME"); // Name of chip
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+    psString extname = NULL;            // Extension name
+    psStringAppend(&extname, "%s_%s_%s", name, chipName, cellName);
+
+    // XXX Could do a table lookup from the camera format, in case the input file isn't laid out with
+    // NAME_CHIP_CELL extension names --- use these as keys, and the value as the proper extension name.
+    // Allow interpolation of concepts, e.g., "{CHIP.NAME}" --> "ccd13".
+
+    if (!psFitsMoveExtName(fits, extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", extname);
+        psFree(extname);
+        return 0;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // The FITS header
+    if (!header) {
+        psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", extname);
+        psFree(extname);
+        psFree(header);
+        return 0;
+    }
+
+    psString headerName = NULL;         // Name for header
+    psStringAppend(&headerName, "%s.HEADER", name);
+    if (!psMetadataAdd(cell->analysis, PS_LIST_TAIL, headerName, PS_DATA_METADATA,
+                       "FITS table header", header)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add header from extension %s to analysis metadata "
+                "for chip %s, cell %s\n", extname, chipName, cellName);
+        psFree(headerName);
+        psFree(header);
+        psFree(extname);
+        return 0;
+    }
+    psFree(headerName);
+    psFree(header);
+
+    psArray *table = psFitsReadTable(fits); // The table
+    if (!psMetadataAdd(cell->analysis, PS_LIST_TAIL, name, PS_DATA_ARRAY, "FITS table", table)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add table from extension %s to analysis metadata "
+                "for chip %s, cell %s\n", extname, chipName, cellName);
+        psFree(table);
+        psFree(extname);
+        return 0;
+    }
+
+    psFree(extname);
+    psFree(table);                      // Dropping reference
+
+    return 1;
+}
+
+
+int pmChipReadTable(pmChip *chip, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numRead = 0;                    // Number of reads
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        numRead += pmCellReadTable(cell, fits, name);
+    }
+
+    return numRead;
+}
+
+
+int pmFPAReadTable(pmFPA *fpa, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numRead = 0;                    // Number of reads
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        numRead += pmChipReadTable(chip, fits, name);
+    }
+
+    return numRead;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPARead.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPARead.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPARead.h	(revision 22290)
@@ -0,0 +1,157 @@
+/* @file pmFPARead.h
+ * @brief Functions to read FPA components from a FITS file
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-12 22:22:33 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_READ_H
+#define PM_FPA_READ_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Read a readout incrementally
+///
+/// Multiple calls to this function moves through a readout within a cell incrementally.  It is required to
+/// pass the readout previously acquired (or a newly-allocated one) in order to preserve state information
+/// (where the read is at).  Only a maximum of numRows rows are read at a time.  Returns true if pixels are
+/// read, and false otherwise.  To facilitate looping, no error is generated for reading a plane that doesn't
+/// exist.  Note that this function doesn't put pixels in the HDU.  It is therefore NOT COMPATIBLE with
+/// pmCellWrite, pmChipWrite, and pmFPAWrite (or any function that uses or creates an HDU for that matter).
+/// Use pmReadoutWriteNext to write the data that's read by this function.  This function is intended for
+/// reading in many readouts into memory at once (e.g., for stacking) where the input is not written out.
+bool pmReadoutReadNext(bool *status,	// non-error exit condition?
+		       pmReadout *readout, // Readout into which to read
+                       psFits *fits,    // FITS file from which to read
+                       int z,           // Readout number/plane; zero-offset indexing
+                       int numRows      // The number of rows to read
+                      );
+
+/// Read an entire cell
+///
+/// Reads the appropriate HDU, ingests concepts from the header, and portions pixels into readouts.  Pixels
+/// are converted to F32.
+bool pmCellRead(pmCell *cell,           // Cell to read into
+                psFits *fits,           // FITS file from which to read
+                psDB *db                // Database handle, for "concepts" ingest
+               );
+
+/// Read a chip
+///
+/// Iterates over component cells, reading each with pmCellRead.
+bool pmChipRead(pmChip *chip,           // Chip to read into
+                psFits *fits,           // FITS file from which to read
+                psDB *db                // Database handle, for "concepts" ingest
+               );
+
+/// Read an FPA
+///
+/// Iterates over component chips, reading each with pmChipRead.
+bool pmFPARead(pmFPA *fpa,              // FPA to read into
+               psFits *fits,            // FITS file from which to read
+               psDB *db                 // Database handle, for "concepts" ingest
+              );
+
+/// Read an entire cell into the mask
+///
+/// Same as pmCellRead, but reads into the mask element of the readouts.
+bool pmCellReadMask(pmCell *cell,           // Cell to read into
+                    psFits *fits,           // FITS file from which to read
+                    psDB *db                // Database handle, for "concepts" ingest
+                   );
+
+/// Read an entire chip into the mask
+///
+/// Same as pmChipRead, but reads into the mask element of the readouts.
+bool pmChipReadMask(pmChip *chip,           // Chip to read into
+                    psFits *fits,           // FITS file from which to read
+                    psDB *db                // Database handle, for "concepts" ingest
+                   );
+
+/// Read an entire FPA into the mask
+///
+/// Same as pmFPARead, but reads into the mask element of the readouts.
+bool pmFPAReadMask(pmFPA *fpa,              // FPA to read into
+                   psFits *fits,            // FITS file from which to read
+                   psDB *db                 // Database handle, for "concepts" ingest
+                  );
+
+/// Read an entire cell into the weight
+///
+/// Same as pmCellRead, but reads into the weight element of the readouts.
+bool pmCellReadWeight(pmCell *cell,           // Cell to read into
+                      psFits *fits,           // FITS file from which to read
+                      psDB *db                // Database handle, for "concepts" ingest
+                     );
+
+/// Read an entire chip into the weight
+///
+/// Same as pmChipRead, but reads into the weight element of the readouts.
+bool pmChipReadWeight(pmChip *chip,           // Chip to read into
+                      psFits *fits,           // FITS file from which to read
+                      psDB *db                // Database handle, for "concepts" ingest
+                     );
+
+/// Read an entire FPA into the weight
+///
+/// Same as pmFPARead, but reads into the weight element of the readouts.
+bool pmFPAReadWeight(pmFPA *fpa,              // FPA to read into
+                     psFits *fits,            // FITS file from which to read
+                     psDB *db                 // Database handle, for "concepts" ingest
+                    );
+
+/// Read cell headers
+///
+/// Same as pmCellRead, but reads only the headers of the readouts.
+bool pmCellReadHeaderSet(pmCell *cell,           // Cell to read into
+			 psFits *fits,           // FITS file from which to read
+			 psDB *db                // Database handle, for "concepts" ingest
+    );
+
+/// Read chip headers
+///
+/// Same as pmChipRead, but reads only the headers of the readouts.
+bool pmChipReadHeaderSet(pmChip *chip,           // Chip to read into
+                      psFits *fits,           // FITS file from which to read
+                      psDB *db                // Database handle, for "concepts" ingest
+                     );
+
+/// Read FPA headers
+///
+/// Same as pmFPARead, but reads only the headers of the readouts.
+bool pmFPAReadHeaderSet(pmFPA *fpa,              // FPA to read into
+			psFits *fits,            // FITS file from which to read
+			psDB *db                 // Database handle, for "concepts" ingest
+    );
+
+/// Read a FITS table into the cell
+///
+/// Given a name, which is combined with the chip and cell to identify the extension name ("NAME_CHIP_CELL"),
+/// read the FITS table into the cell analysis metadata (with key being the provided name).  The header is
+/// also read and included in the cell analysis metadata under "name.HEADER".
+int pmCellReadTable(pmCell *cell,       ///< Cell for which to read table
+                    psFits *fits,       ///< FITS file from which the table
+                    const char *name    ///< Specifies the extension name, and target in the analysis metadata
+                   );
+
+/// Read a FITS table into the component cells
+///
+/// Iterates over component cells, calling pmCellReadTable.
+int pmChipReadTable(pmChip *chip,       ///< Cell for which to read table
+                    psFits *fits,       ///< FITS file from which the table
+                    const char *name    ///< Specifies the extension name, and target in the analysis metadata
+                   );
+
+/// Read a FITS table into the component cells
+///
+/// Iterates over component chips, calling pmChipReadTable.
+int pmFPAReadTable(pmFPA *fpa,          ///< Cell for which to read table
+                   psFits *fits,        ///< FITS file from which the table
+                   const char *name     ///< Specifies the extension name, and target in the analysis metadata
+                  );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAUtils.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAUtils.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAUtils.c	(revision 22290)
@@ -0,0 +1,55 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAUtils.h"
+
+int pmFPAFindChip(const pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, -1);
+    PS_ASSERT_PTR_NON_NULL(name, -1);
+    if (strlen(name) == 0) {
+        return -1;
+    }
+
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i]; // The chip of interest
+        psString testName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of this chip
+        if (strcmp(name, testName) == 0) {
+            return i;
+        }
+    }
+
+    psError(PS_ERR_IO, true, "Unable to find chip %s\n", name);
+    return -1;
+}
+
+
+int pmChipFindCell(const pmChip *chip, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, -1);
+    PS_ASSERT_PTR_NON_NULL(name, -1);
+    if (strlen(name) == 0) {
+        return -1;
+    }
+
+    psArray *cells = chip->cells;    // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i]; // The cell of interest
+        psString testName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of this cell
+        if (strcmp(name, testName) == 0) {
+            return i;
+        }
+    }
+
+    psError(PS_ERR_IO, true, "Unable to find cell %s\n", name);
+    return -1;
+}
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAUtils.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAUtils.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAUtils.h	(revision 22290)
@@ -0,0 +1,33 @@
+/* @file pmFPAUtils.h
+ * @brief Utility functions for FPAs
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_UTILS_H
+#define PM_FPA_UTILS_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Find a chip by name; return the index
+///
+/// Looks for a chip within the FPA with CHIP.NAME matching the provided name.  Returns the index of the chip,
+/// or -1 if it was not found.
+int pmFPAFindChip(const pmFPA *fpa,     ///< FPA in which to find the chip
+                  const char *name      ///< Name of the chip
+                 );
+
+/// Find a cell by name; return the index
+///
+/// Looks for a cell within the chip with CELL.NAME matching the provided name.  Returns the index of the
+/// cell, or -1 if it was not found.
+int pmChipFindCell(const pmChip *chip,  // Chip in which to find the cell
+                   const char *name     // Name of the cell
+                  );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAWrite.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAWrite.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAWrite.c	(revision 22290)
@@ -0,0 +1,508 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmDetrendDB.h"
+#include "pmFPAfile.h"
+#include "pmHDUUtils.h"
+#include "pmHDUGenerate.h"
+#include "pmConcepts.h"
+
+#include "pmFPAWrite.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Definitions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Specify what to read
+typedef enum {
+    FPA_WRITE_TYPE_IMAGE,               // Write image
+    FPA_WRITE_TYPE_MASK,                // Write mask
+    FPA_WRITE_TYPE_WEIGHT               // Write weight map
+} fpaWriteType;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// Update the FPA.NAME, CHIP.NAME and CELL.NAME in the FITS header, if required
+bool pmFPAUpdateNames(pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    pmHDU *hdu = pmHDUGetHighest(fpa, chip, cell); // Highest HDU, i.e., the PHU
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find HDU.\n");
+        return false;
+    }
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+    bool mdok;                          // Status of MD lookup
+    psMetadata *fileData = psMetadataLookupMetadata(&mdok, hdu->format, "FILE"); // File information
+    if (!mdok || !fileData) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FILE information in camera format.\n");
+        return false;
+    }
+    if (fpa) {
+        const char *fpaNameHdr = psMetadataLookupStr(&mdok, fileData, "FPA.NAME");
+        if (mdok && fpaNameHdr && strlen(fpaNameHdr) > 0) {
+            const char *fpaName = psMetadataLookupStr(NULL, fpa->concepts, "FPA.NAME");
+            psMetadataAddStr(hdu->header, PS_LIST_TAIL, fpaNameHdr, PS_META_REPLACE, "FPA name", fpaName);
+        }
+    }
+
+    if (fpa && !fpa->hdu && (chip || cell)) {
+        const char *rule = psMetadataLookupStr(NULL, fileData, "CONTENT.RULE"); // How to define the CONTENT
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find CONTENT.RULE in FILE in camera format.");
+            return false;
+        }
+
+        pmFPAview *view = pmFPAviewGenerate(fpa, chip, cell, NULL); // View for fpa, chip, cell
+        psString content = pmFPANameFromRule(rule, fpa, view); // Content of this file, specified by the rule
+        psFree(view);
+
+        const char *contentKey = psMetadataLookupStr(NULL, fileData, "CONTENT"); // The CONTENT header keyword
+        if (!contentKey) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find CONTENT in FILE in the camera format.");
+            psFree(content);
+            return false;
+        }
+
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, contentKey, PS_META_REPLACE, "Content of file", content);
+        psFree(content);                // Drop reference
+    }
+
+    return true;
+}
+
+// Return the appropriate image array for the given type
+static psArray **appropriateImageArray(pmHDU *hdu, // HDU containing the image arrays
+                                       fpaWriteType type // Type to write
+                                      )
+{
+    switch (type) {
+    case FPA_WRITE_TYPE_IMAGE:
+        return &hdu->images;
+    case FPA_WRITE_TYPE_MASK:
+        return &hdu->masks;
+    case FPA_WRITE_TYPE_WEIGHT:
+        return &hdu->weights;
+    default:
+        psAbort("Unknown write type: %x\n", type);
+    }
+    return NULL;
+}
+
+// Run the appropriate HDU write function
+static bool appropriateWriteFunc(pmHDU *hdu, // HDU to write
+                                 psFits *fits, // FITS file to which to write
+                                 fpaWriteType type // Type to write
+                                )
+{
+    switch (type) {
+    case FPA_WRITE_TYPE_IMAGE:
+        return pmHDUWrite(hdu, fits);
+    case FPA_WRITE_TYPE_MASK:
+        return pmHDUWriteMask(hdu, fits);
+    case FPA_WRITE_TYPE_WEIGHT:
+        return pmHDUWriteWeight(hdu, fits);
+    default:
+        psAbort("Unknown write type: %x\n", type);
+    }
+    return false;
+}
+// Write a cell image/mask/weight
+static bool cellWrite(pmCell *cell,     // Cell to write
+                      psFits *fits,     // FITS file to which to write
+                      psDB *db,         // Database handle for "concepts" update
+                      bool blank,       // Write a blank PHU?
+                      fpaWriteType type // Type to write
+                     )
+{
+    assert(cell);
+    assert(fits);
+
+    psTrace ("pmFPAWrite", 5, "writing to Cell (%d)\n", blank);
+
+    pmHDU *hdu = cell->hdu;             // The HDU
+    if (!hdu || !cell->data_exists) {
+        return true;                    // We wrote every HDU that exists
+    }
+
+    psArray **imageArray = appropriateImageArray(hdu, type); // Array of images in the HDU
+
+    // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+    // generate the HDU, but only copies the structure.
+    if (!blank && !hdu->blankPHU && !*imageArray && (!pmHDUGenerateForCell(cell) || !*imageArray)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell --- likely programming error.\n");
+        return false;
+    }
+
+    // We only write out a blank PHU if it's specifically requested.
+    bool writeBlank = blank && hdu->blankPHU && !*imageArray; // Write a blank PHU?
+    bool writeImage = !blank && !hdu->blankPHU && *imageArray; // Write an image?
+
+    if (writeBlank || writeImage) {
+
+        pmFPAUpdateNames(cell->parent->parent, cell->parent, cell);
+        pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                                 PM_CONCEPT_SOURCE_DEFAULTS;
+        if (!pmConceptsWriteCell(cell, source, true, NULL)) {
+            psError(PS_ERR_IO, false, "Unable to write concepts for cell.\n");
+            return false;
+        }
+        if (!appropriateWriteFunc(hdu, fits, type)) {
+            psError(PS_ERR_IO, false, "Unable to write HDU for cell.\n");
+            return false;
+        }
+    }
+    // No lower levels to which to recurse
+
+    return true;
+}
+
+// Write a chip image/mask/weight
+static bool chipWrite(pmChip *chip,     // Chip to write
+                      psFits *fits,     // FITS file to which to write
+                      psDB *db,         // Database handle for "concepts" update
+                      bool blank,       // Write a blank PHU?
+                      bool recurse,     // Recurse to lower levels?
+                      fpaWriteType type // Type to write
+                     )
+{
+    assert(chip);
+    assert(fits);
+
+    pmHDU *hdu = chip->hdu;             // The HDU
+
+    psTrace ("pmFPAWrite", 5, "writing to Chip (%d, %d)\n", blank, recurse);
+
+    // If we have data at this level, try to write it out
+    if (hdu && chip->data_exists) {
+        psArray **imageArray = appropriateImageArray(hdu, type); // Array of images in HDU
+
+        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+        // generate the HDU, but only copies the structure.
+        if (!blank && !hdu->blankPHU && !*imageArray && (!pmHDUGenerateForChip(chip) || !*imageArray)) {
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to generate HDU for chip --- likely programming error.\n");
+            return false;
+        }
+
+        // We only write out a blank PHU if it's specifically requested.
+        bool writeBlank = blank && hdu->blankPHU && !*imageArray; // Write a blank HDU?
+        bool writeImage = !blank && !hdu->blankPHU && *imageArray; // Write an image?
+
+        if (writeBlank || writeImage) {
+            pmFPAUpdateNames(chip->parent, chip, NULL);
+            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                                     PM_CONCEPT_SOURCE_DEFAULTS;
+            if (!pmConceptsWriteChip(chip, source, true, true, NULL)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for chip.\n");
+                return false;
+            }
+            if (!appropriateWriteFunc(hdu, fits, type)) {
+                psError(PS_ERR_IO, false, "Unable to write HDU for chip.\n");
+                return false;
+            }
+        }
+    }
+
+    // Recurse to lower level if specifically requested.
+    // XXX recursion implies blank == false (must be called on correct level?)
+    if (recurse) {
+        psArray *cells = chip->cells;       // Array of cells
+        for (int i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i];  // The cell of interest
+            if (!cellWrite(cell, fits, db, false, type)) {
+                psError(PS_ERR_IO, false, "Unable to write Chip.\n");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+
+// Write an FPA image/mask/weight
+static bool fpaWrite(pmFPA *fpa,        // FPA to write
+                     psFits *fits,      // FITS file to which to write
+                     psDB *db,          // Database handle for "concepts" update
+                     bool blank,        // Write a blank PHU?
+                     bool recurse,      // Recurse to lower levels?
+                     fpaWriteType type  // Type to write
+                    )
+{
+    assert(fpa);
+    assert(fits);
+
+    pmHDU *hdu = fpa->hdu;              // The HDU
+
+    psTrace ("pmFPAWrite", 5, "writing to FPA (%d, %d)\n", blank, recurse);
+
+    // If we have data at this level, try to write it out
+    if (hdu) {
+        psArray **imageArray = appropriateImageArray(hdu, type); // Array of images in HDU
+
+        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+        // generate the HDU, but only copies the structure.
+        if (!blank && !hdu->blankPHU && !*imageArray && (!pmHDUGenerateForFPA(fpa) || !*imageArray)) {
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to generate HDU for FPA --- likely programming error.\n");
+            return false;
+        }
+
+        // We only write out a blank PHU if it's specifically requested.
+        bool writeBlank = blank && hdu->blankPHU && !*imageArray; // Write a blank PHU?
+        bool writeImage = !blank && !hdu->blankPHU && *imageArray; // Write an image?
+
+        if (writeBlank || writeImage) {
+            pmFPAUpdateNames(fpa, NULL, NULL);
+            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                                     PM_CONCEPT_SOURCE_DEFAULTS;
+            if (!pmConceptsWriteFPA(fpa, source, true, NULL)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
+                return false;
+            }
+            if (!appropriateWriteFunc(hdu, fits, type))  {
+                psError(PS_ERR_IO, false, "Unable to write HDU for FPA.\n");
+                return false;
+            }
+        }
+    }
+
+    // Recurse to lower levels if requested
+    if (recurse) {
+        psArray *chips = fpa->chips;        // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i];  // The chip of interest
+            if (!chipWrite(chip, fits, db, false, true, type)) {
+                psError(PS_ERR_IO, false, "Unable to write FPA.\n");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutWriteNext(pmReadout *readout, psFits *fits, int z)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = pmHDUFromReadout(readout); // The HDU to which to write
+    if (!hdu) {
+        psError(PS_ERR_IO, false, "Unable to find HDU for readout.\n");
+        return false;
+    }
+    psMetadata *header = hdu->header;   // The FITS header
+    if (!header) {
+        psError(PS_ERR_IO, true, "No FITS header available in the HDU.\n");
+        return false;
+    }
+
+    // We have to rely to a great extent on the FITS header, because otherwise we simply don't know how many
+    // image planes there are (NAXIS3) or the size of the original image (NAXIS1, NAXIS2).
+    bool mdok = true;                   // Status of MD lookup
+    int naxis1 = psMetadataLookupS32(&mdok, header, "NAXIS1"); // Number of columns
+    if (!mdok || naxis1 <= 0) {
+        psError(PS_ERR_IO, true, "Can't find NAXIS1 in header.\n");
+        return false;
+    }
+    int naxis2 = psMetadataLookupS32(&mdok, header, "NAXIS2"); // Number of rows
+    if (!mdok || naxis2 <= 0) {
+        psError(PS_ERR_IO, true, "Can't find NAXIS2 in header.\n");
+        return false;
+    }
+    int naxis3 = psMetadataLookupS32(&mdok, header, "NAXIS3"); // Number of image planes
+    if (!mdok || naxis3 <= 0) {
+        naxis3 = 1;
+    }
+    if (z >= naxis3) {
+        psError(PS_ERR_IO, true, "Specified a plane number (%d) greater than NAXIS3 allows.\n", z);
+        return false;
+    }
+
+    if (!hdu->images) {
+        psError(PS_ERR_IO, true, "No images allocated in HDU.\n");
+        return false;
+    }
+    psImage *image = readout->image; // The image from the HDU to write
+    if (readout->row0 == 0 && readout->col0 == 0 && z == 0) {
+        // Then we can assume that nothing has been written to the FITS file for now
+        if (naxis1 == image->numCols && naxis2 == image->numRows) {
+            // We can write the whole lot at once
+            return psFitsWriteImage(fits, header, image, z, hdu->extname);
+        }
+        // Create a dummy image so we can write something larger than we actually have
+        psImage *dummy = psImageAlloc(naxis1, naxis2, image->type.type); // Dummy image
+        psImageInit(dummy, 0);
+        psImageOverlaySection(dummy, image, 0, 0, "=");
+        bool result = psFitsWriteImage(fits, header, dummy, z, hdu->extname);
+        psFree(dummy);
+        return result;
+    }
+
+    // We can simply update an existing HDU
+    if (hdu->blankPHU && !psFitsMoveExtNum(fits, 0, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
+        return false;
+    }
+    return psFitsUpdateImage(fits, image, readout->col0, readout->row0, z);
+}
+
+
+bool pmCellWrite(pmCell *cell, psFits *fits, psDB *db, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return cellWrite(cell, fits, db, blank, FPA_WRITE_TYPE_IMAGE);
+}
+
+bool pmChipWrite(pmChip *chip, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return chipWrite(chip, fits, db, blank, recurse, FPA_WRITE_TYPE_IMAGE);
+}
+
+bool pmFPAWrite(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return fpaWrite(fpa, fits, db, blank, recurse, FPA_WRITE_TYPE_IMAGE);
+}
+
+
+bool pmCellWriteMask(pmCell *cell, psFits *fits, psDB *db, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return cellWrite(cell, fits, db, blank, FPA_WRITE_TYPE_MASK);
+}
+
+bool pmChipWriteMask(pmChip *chip, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return chipWrite(chip, fits, db, blank, recurse, FPA_WRITE_TYPE_MASK);
+}
+
+bool pmFPAWriteMask(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return fpaWrite(fpa, fits, db, blank, recurse, FPA_WRITE_TYPE_MASK);
+}
+
+
+bool pmCellWriteWeight(pmCell *cell, psFits *fits, psDB *db, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return cellWrite(cell, fits, db, blank, FPA_WRITE_TYPE_WEIGHT);
+}
+
+bool pmChipWriteWeight(pmChip *chip, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return chipWrite(chip, fits, db, blank, recurse, FPA_WRITE_TYPE_WEIGHT);
+}
+
+bool pmFPAWriteWeight(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return fpaWrite(fpa, fits, db, blank, recurse, FPA_WRITE_TYPE_WEIGHT);
+}
+
+
+int pmCellWriteTable(psFits *fits, const pmCell *cell, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    const char *chipName = psMetadataLookupStr(NULL, cell->parent->concepts, "CHIP.NAME"); // Name of chip
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+
+    psArray *table = psMetadataLookupPtr(NULL, cell->analysis, name); // The FITS table
+    if (!table) {
+        // We wrote everything we could find
+        return 0;
+    }
+
+    psString headerName = NULL;         // Name for header in analysis metadata
+    psStringAppend(&headerName, "%s.HEADER", name);
+    psMetadata *header = psMetadataLookupMetadata(NULL, cell->analysis, headerName); // The FITS header
+    psFree(headerName);
+
+    psString extname = NULL;            // Extension name
+    psStringAppend(&extname, "%s_%s_%s", name, chipName, cellName);
+
+    // XXX Could do a table lookup from the camera format, in case the input file isn't laid out with
+    // NAME_CHIP_CELL extension names --- use these as keys, and the value as the proper extension name.
+    // Allow interpolation of concepts, e.g., "{CHIP.NAME}" --> "ccd13".
+
+    if (!psFitsWriteTable(fits, header, table, extname)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to write table from chip %s, cell %s to extension %s\n",
+                chipName, cellName, extname);
+        psFree(extname);
+        return 0;
+    }
+
+    psFree(extname);
+    return 1;
+}
+
+
+int pmChipWriteTable(psFits *fits, const pmChip *chip, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numWrite = 0;                    // Number of reads
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        numWrite += pmCellWriteTable(fits, cell, name);
+    }
+
+    return numWrite;
+}
+
+
+int pmFPAWriteTable(psFits *fits, const pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numWrite = 0;                    // Number of reads
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        numWrite += pmChipWriteTable(fits, chip, name);
+    }
+
+    return numWrite;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAWrite.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAWrite.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAWrite.h	(revision 22290)
@@ -0,0 +1,169 @@
+/* @file pmFPAWrite.h
+ * @brief Write FPA components to a FITS file
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-31 04:17:41 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_WRITE_H
+#define PM_FPA_WRITE_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Write a readout incrementally
+///
+/// Writes a readout to a FITS file, perhaps incrementally (if it has been read in using pmReadoutReadNext).
+/// Relies on the FITS header to specify how many image planes there are, and the width and height.
+bool pmReadoutWriteNext(pmReadout *readout, ///< Readout to write
+                        psFits *fits,   ///<  FITS file to which to write
+                        int z           ///<  Image plane to write
+                       );
+
+/// Write a cell to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU pixels if required.  A blank (i.e., image-less header) is
+/// written only if specifically requested.  Writes the concepts to the various locations, and then the HDU to
+/// the FITS file.  This function should be called at the beginning of the output cell loop with blank=true in
+/// order to produce the correct file structure.
+bool pmCellWrite(pmCell *cell,          ///<  Cell to write
+                 psFits *fits,          ///<  FITS file to which to write
+                 psDB *db,              ///<  Database handle for "concepts" update
+                 bool blank             ///<  Write a blank PHU?
+                );
+
+/// Write a chip to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU pixels if required.  A blank (i.e., image-less header) is
+/// written only if specifically requested.  Writes the concepts to the various locations, and then the HDU to
+/// the FITS file, optionally recursing to lower levels.  This function should be called at the beginning of
+/// the output chip loop with blank=true and recurse=false in order to produce the correct file structure.
+bool pmChipWrite(pmChip *chip,          ///<  Chip to write
+                 psFits *fits,          ///<  FITS file to which to write
+                 psDB *db,              ///<  Database handle for "concepts" update
+                 bool blank,            ///<  Write a blank PHU?
+                 bool recurse           ///<  Recurse to lower levels?
+                );
+
+/// Write an FPA to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU pixels if required.  A blank (i.e., image-less header) is
+/// written only if specifically requested.  Writes the concepts to the various locations, and then the HDU to
+/// the FITS file, optionally recursing to lower levels.  This function should be called at the beginning of
+/// the output FPA loop with blank=true and recurse=false in order to produce the correct file structure.
+bool pmFPAWrite(pmFPA *fpa,             ///<  FPA to write
+                psFits *fits,           ///<  FITS file to which to write
+                psDB *db,               ///<  Database handle for "concepts" update
+                bool blank,             ///<  Write a blank PHU?
+                bool recurse            ///<  Recurse to lower levels?
+               );
+
+/// Write a cell mask to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU mask pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU mask to the FITS file.  This function should be called at the beginning of the output cell loop
+/// with blank=true in order to produce the correct file structure.
+bool pmCellWriteMask(pmCell *cell,      ///<  Cell to write
+                     psFits *fits,      ///<  FITS file to which to write
+                     psDB *db,          ///<  Database handle for "concepts" update
+                     bool blank         ///<  Write a blank PHU?
+                    );
+
+/// Write a chip mask to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU mask pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU mask to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output chip loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmChipWriteMask(pmChip *chip,      ///<  Chip to write
+                     psFits *fits,      ///<  FITS file to which to write
+                     psDB *db,          ///<  Database handle for "concepts" update
+                     bool blank,        ///<  Write a blank PHU?
+                     bool recurse       ///<  Recurse to lower levels?
+                    );
+
+/// Write an FPA mask to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU mask pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU mask to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output FPA loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmFPAWriteMask(pmFPA *fpa,         ///<  FPA to write
+                    psFits *fits,       ///<  FITS file to which to write
+                    psDB *db,           ///<  Database handle for "concepts" update
+                    bool blank,         ///<  Write a blank PHU?
+                    bool recurse        ///<  Recurse to lower levels?
+                   );
+
+/// Write a cell weight to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU weight pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU weight to the FITS file.  This function should be called at the beginning of the output cell loop
+/// with blank=true in order to produce the correct file structure.
+bool pmCellWriteWeight(pmCell *cell,    ///<  Cell to write
+                       psFits *fits,    ///<  FITS file to which to write
+                       psDB *db,        ///<  Database handle for "concepts" update
+                       bool blank       ///<  Write a blank PHU?
+                      );
+
+/// Write a chip weight to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU weight pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU weight to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output chip loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmChipWriteWeight(pmChip *chip,    ///<  Chip to write
+                       psFits *fits,    ///<  FITS file to which to write
+                       psDB *db,        ///<  Database handle for "concepts" update
+                       bool blank,      ///<  Write a blank PHU?
+                       bool recurse     ///<  Recurse to lower levels?
+                      );
+
+/// Write an FPA weight to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU weight pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU weight to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output FPA loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmFPAWriteWeight(pmFPA *fpa,       ///<  FPA to write
+                      psFits *fits,     ///<  FITS file to which to write
+                      psDB *db,         ///<  Database handle for "concepts" update
+                      bool blank,       ///<  Write a blank PHU?
+                      bool recurse      ///<  Recurse to lower levels?
+                     );
+
+
+/// Write a FITS table from the cell's analysis metadata.
+///
+/// The FITS table (a psArray of psMetadatas) from the cell's analysis metadata (under "name") is written to
+/// the FITS file, at an extension specified by the name, chip name and cell name ("NAME_CHIP_CELL").  If a
+/// header is present in the analysis metadata ("name.HEADER"), then it is written also.
+int pmCellWriteTable(psFits *fits,      ///< FITS file to which to write
+                     const pmCell *cell, ///< Cell containing FITS table in the analysis metadata
+                     const char *name   ///< Name for the table data, and the extension name
+                    );
+
+int pmChipWriteTable(psFits *fits,      ///< FITS file to which to write
+                     const pmChip *chip, ///< Chip containing cells with tables to write
+                     const char *name   ///< Name for the table data, and the extension name
+                    );
+
+int pmFPAWriteTable(psFits *fits,       ///< FITS file to which to write
+                    const pmFPA *fpa,   ///< FPA containing cells with tables to write
+                    const char *name    ///< Name for the table data, and the extension name
+                   );
+
+// XXX better name, please
+bool pmFPAUpdateNames(pmFPA *fpa, pmChip *chip, pmCell *cell);
+
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_JPEG.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_JPEG.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_JPEG.c	(revision 22290)
@@ -0,0 +1,225 @@
+/** @file  pmFPA_JPEG.c
+ *
+ * This file contains functions to write JPEG images.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.23 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-09-21 00:02:11 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPA_JPEG.h"
+
+
+bool pmFPAviewWriteJPEG(const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWriteJPEG (fpa, view, file, config);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWriteJPEG (chip, view, file, config);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWriteJPEG (cell, view, file, config);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutWriteJPEG (readout, view, file, config);
+    return true;
+}
+
+// read in all chip-level JPEG files for this FPA
+bool pmFPAWriteJPEG (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        pmChipWriteJPEG (chip, view, file, config);
+    }
+    return true;
+}
+
+// read in all cell-level JPEG files for this chip
+bool pmChipWriteJPEG (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        pmCellWriteJPEG (cell, view, file, config);
+    }
+    return true;
+}
+
+// read in all readout-level JPEG files for this cell
+bool pmCellWriteJPEG (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutWriteJPEG (readout, view, file, config);
+    }
+    return true;
+}
+
+// write JPEG image for readout
+// note that this function will try as hard a possible to write out a jpeg.  it will not fail
+// just because the values are in a poor range.  it is more convenient to know you are getting
+// a jpeg which is weird than to fail to get the file at all.
+bool pmReadoutWriteJPEG (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    bool status;
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    if (file->type != PM_FPA_FILE_JPEG) {
+        psError(PS_ERR_UNKNOWN, true, "warning: type mismatch");
+        return false;
+    }
+
+    psTrace("psModules.camera", 3, "writing jpeg for readout %zd\n", (size_t) readout);
+
+    psMetadata *recipe = psMetadataLookupMetadata(&status, config->recipes, "PPIMAGE");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "missing recipe PPIMAGE in config data");
+        return false;
+    }
+
+    psMetadata *options = psMetadataLookupMetadata(&status, recipe, file->name);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "missing %s options in PPIMAGE recipe", file->name);
+        return false;
+    }
+
+    float min = 0;
+    float max = 0;                 // Minimum and maximum for stretch
+    float mean = 0;
+    float delta = 0;
+
+    // measure the image statistics for scaling
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    stats->nSubsample = 10000;
+    if (!psImageBackground(stats, NULL, readout->image, NULL, 0, rng)) {
+        psStats *statsAlt = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        stats->nSubsample = 10000;
+        if (!psImageBackground(stats, NULL, readout->image, NULL, 0, rng)) {
+            psLogMsg("jpeg", PS_LOG_WARN, "Unable to measure statistics for image, writing blank jpeg\n");
+            mean = 0;
+            delta = 1;
+        } else {
+            mean = statsAlt->sampleMean;
+            delta = 2*statsAlt->sampleStdev;
+        }
+        psFree(statsAlt);
+    } else {
+        mean = stats->robustMedian;
+        delta = stats->robustUQ - stats->robustLQ;
+    }
+    psFree(rng);
+    psFree(stats);
+
+    char *colormapName = psMemIncrRefCounter (psMetadataLookupStr (NULL, options, "COLORMAP"));
+    if (!colormapName)
+        colormapName = psStringCopy ("-greyscale");
+    char *mode = psMemIncrRefCounter (psMetadataLookupStr (NULL, options, "SCALE.MODE"));
+    if (!mode)
+        mode = psStringCopy ("RANGE");
+    float fmin = psMetadataLookupF32 (&status, options, "SCALE.MIN");
+    if (!status)
+        fmin = -3.0;
+    float fmax = psMetadataLookupF32 (&status, options, "SCALE.MAX");
+    if (!status)
+        fmax = +6.0;
+
+    if (!strcasecmp (mode, "RANGE")) {
+        min = mean + fmin*delta;
+        max = mean + fmax*delta;
+    }
+    if (!strcasecmp (mode, "FRACTION")) {
+        min = fmin*mean;
+        max = fmax*mean;
+    }
+    if (!strcasecmp (mode, "VALUE")) {
+        min = fmin;
+        max = fmax;
+    }
+
+    if (!isfinite(min) || !isfinite(max)) {
+        psLogMsg("jpeg", PS_LOG_WARN, "The stretch parameters are not both finite, writing blank jpeg\n");
+        min = 0;
+        max = 1;
+    }
+
+    psImageJpegColormap *map = psImageJpegColormapSet (NULL, colormapName);
+    if (!map) {
+        map = psImageJpegColormapSet (NULL, "-greyscale");
+    }
+
+    psImageJpeg (map, readout->image, file->filename, min, max);
+
+    psFree(map);
+    psFree(colormapName);
+    psFree(mode);
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_JPEG.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_JPEG.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_JPEG.h	(revision 22290)
@@ -0,0 +1,24 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-04-14 03:22:47 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_JPEG_H
+#define PM_FPA_JPEG_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+bool pmFPAviewWriteJPEG (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmFPAWriteJPEG (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmChipWriteJPEG (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmCellWriteJPEG (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmReadoutWriteJPEG (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+/// @}
+# endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_MANAPLOT.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_MANAPLOT.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_MANAPLOT.c	(revision 22290)
@@ -0,0 +1,176 @@
+/** @file  pmFPA_MANAPLOT.c
+ *
+ * This file contains functions to write MANAPLOT images.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-03-30 21:12:56 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAfile.h"
+#include "pmFPAview.h"
+#include "pmFPA_MANAPLOT.h"
+
+
+bool pmFPAviewWriteMANAPLOT(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWriteMANAPLOT (fpa, view, file, config);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWriteMANAPLOT (chip, view, file, config);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWriteMANAPLOT (cell, view, file, config);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutWriteMANAPLOT (readout, view, file, config);
+    return true;
+}
+
+// read in all chip-level MANAPLOT files for this FPA
+bool pmFPAWriteMANAPLOT (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        pmChipWriteMANAPLOT (chip, view, file, config);
+    }
+    return true;
+}
+
+// read in all cell-level MANAPLOT files for this chip
+bool pmChipWriteMANAPLOT (pmChip *chip, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        pmCellWriteMANAPLOT (cell, view, file, config);
+    }
+    return true;
+}
+
+// read in all readout-level MANAPLOT files for this cell
+bool pmCellWriteMANAPLOT (pmCell *cell, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutWriteMANAPLOT (readout, view, file, config);
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutWriteMANAPLOT (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    if (file->type != PM_FPA_FILE_MANAPLOT) {
+        psError(PS_ERR_UNKNOWN, true, "warning: type mismatch");
+        return false;
+    }
+
+    // XXX does not have to be a mana script...
+    // this function will call a mana script of the form:
+    // scriptname (input) (output)
+    // scriptname : extname
+    // input : filextra
+    // output : filerule
+
+    bool create = file->mode == PM_FPA_MODE_WRITE ? true : false;
+
+    psString tmpName;
+    tmpName = pmFPAfileNameFromRule (file->filextra, file, view);
+    psString input = pmConfigConvertFilename (tmpName, config, create);
+    psFree (tmpName);
+
+    tmpName = pmFPAfileNameFromRule (file->filerule, file, view);
+    psString output = pmConfigConvertFilename (tmpName, config, create);
+    psFree (tmpName);
+
+    tmpName = pmFPAfileNameFromRule (file->extname, file, view);
+    psString script = pmConfigConvertFilename (tmpName, config, create);
+    psFree (tmpName);
+
+    psString line = NULL;
+    psStringAppend (&line, "%s %s %s", script, input, output);
+
+    // capture the stdout and stderr?
+    // XXX use psPipe instead?
+    int status = system (line);
+    if (status == -1) {
+        psError(PS_ERR_UNKNOWN, true, "fork failure: %s", script);
+        return false;
+    }
+    if (WEXITSTATUS(status) != 0) {
+        psError(PS_ERR_UNKNOWN, true, "error running: %s", script);
+        return false;
+    }
+
+    psFree(line);
+    psFree(input);
+    psFree(output);
+    psFree(script);
+
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_MANAPLOT.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_MANAPLOT.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPA_MANAPLOT.h	(revision 22290)
@@ -0,0 +1,24 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:14 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_MANAPLOT_H
+#define PM_FPA_MANAPLOT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+bool pmFPAviewWriteMANAPLOT (const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmFPAWriteMANAPLOT (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmChipWriteMANAPLOT (pmChip *chip, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmCellWriteMANAPLOT (pmCell *cell, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmReadoutWriteMANAPLOT (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+
+/// @}
+# endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfile.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfile.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfile.c	(revision 22290)
@@ -0,0 +1,484 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPACopy.h"
+#include "pmConcepts.h"
+
+static void pmFPAfileFree(pmFPAfile *file)
+{
+  if (!file) {
+    return;
+  }
+
+  psTrace ("pmFPAfileFree", 5, "freeing %s\n", file->name);
+  psFree (file->fpa);
+  psFree (file->src);
+  psFree (file->readout);
+  psFree (file->names);
+
+  psFree (file->camera);
+  psFree (file->cameraName);
+  psFree (file->format);
+  psFree (file->formatName);
+  psFree (file->name);
+
+  if (file->fits != NULL) {
+    psFitsClose (file->fits);
+  }
+  psFree(file->compression);
+
+  psFree (file->filerule);
+
+  psFree (file->filesrc);
+  psFree (file->detrend);
+
+  psFree (file->filename);
+  psFree (file->extname);
+
+  return;
+}
+
+pmFPAfile *pmFPAfileAlloc()
+{
+  pmFPAfile *file = psAlloc(sizeof(pmFPAfile));
+  psMemSetDeallocator(file, (psFreeFunc) pmFPAfileFree);
+
+  file->wrote_phu = false;
+  file->readout = NULL;
+  file->header = NULL;
+
+  file->fileLevel = PM_FPA_LEVEL_NONE;
+  file->dataLevel = PM_FPA_LEVEL_NONE;
+  file->freeLevel = PM_FPA_LEVEL_NONE;
+  file->mosaicLevel = PM_FPA_LEVEL_NONE;
+
+  file->type = PM_FPA_FILE_NONE;
+  file->mode = PM_FPA_MODE_NONE;
+  file->state = PM_FPA_STATE_CLOSED;
+
+  file->fpa = NULL;
+  file->fits = NULL;
+  file->compression = NULL;
+  file->bitpix = 0;
+  file->floatType = PS_FITS_FLOAT_NONE;
+  file->names = psMetadataAlloc();
+
+  file->camera = NULL;
+  file->cameraName = NULL;
+  file->format = NULL;
+  file->formatName = NULL;
+  file->name = NULL;
+
+  file->filerule = NULL;
+
+  file->filename = NULL;
+  file->extname  = NULL;
+
+  file->filesrc = NULL;
+  file->detrend = NULL;
+
+  file->xBin = 1;
+  file->yBin = 1;
+  file->src = NULL;
+
+  file->save = false;
+
+  return file;
+}
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name)
+{
+  PS_ASSERT_PTR_NON_NULL(files, false);
+  PS_ASSERT_PTR_NON_NULL(view, false);
+  PS_ASSERT_PTR_NON_NULL(name, false);
+  PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+  bool status;
+
+  pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+  if (file == NULL) {
+    return NULL;
+  }
+
+  // internal files have the readout as a separate element:
+  if (file->mode == PM_FPA_MODE_INTERNAL) {
+    return file->readout;
+  }
+
+  pmReadout *readout = pmFPAviewThisReadout (view, file->fpa);
+  return readout;
+}
+
+// select the cell from the named pmFPAfile; if the named file does not exist,
+pmCell *pmFPAfileThisCell (psMetadata *files, const pmFPAview *view, const char *name)
+{
+  PS_ASSERT_PTR_NON_NULL(files, false);
+  PS_ASSERT_PTR_NON_NULL(view, false);
+  PS_ASSERT_PTR_NON_NULL(name, false);
+  PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+  bool status;
+
+  pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+  if (file == NULL) {
+    return NULL;
+  }
+
+  // internal files have the readout as a separate element:
+  if (file->mode == PM_FPA_MODE_INTERNAL) {
+    return NULL;
+  }
+
+  pmCell *cell = pmFPAviewThisCell(view, file->fpa);
+  return cell;
+}
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmChip *pmFPAfileThisChip (psMetadata *files, const pmFPAview *view, const char *name)
+{
+  PS_ASSERT_PTR_NON_NULL(files, false);
+  PS_ASSERT_PTR_NON_NULL(view, false);
+  PS_ASSERT_PTR_NON_NULL(name, false);
+  PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+  bool status;
+
+  pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+  if (file == NULL) {
+    return NULL;
+  }
+
+  // internal files have the readout as a separate element:
+  if (file->mode == PM_FPA_MODE_INTERNAL) {
+    return NULL;
+  }
+
+  pmChip *chip = pmFPAviewThisChip (view, file->fpa);
+  return chip;
+}
+
+psString pmFPANameFromRule(const char *rule, const pmFPA *fpa, const pmFPAview *view)
+{
+  PS_ASSERT_STRING_NON_EMPTY(rule, NULL);
+  PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+  psString newName = NULL;            // New name, to be returned
+  newName = psStringCopy(rule);
+
+  if (strstr (newName, "{FPA.NAME}") != NULL) {
+    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.NAME");
+    if (name != NULL) {
+      psStringSubstitute(&newName, "fpa", "{FPA.NAME}");
+    }
+  }
+  if (strstr (newName, "{CHIP.NAME}") != NULL) {
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    if (chip != NULL) {
+      char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+      if (name != NULL) {
+	psStringSubstitute(&newName, name, "{CHIP.NAME}");
+      }
+    }
+  }
+  if (strstr (newName, "{CHIP.ID}") != NULL) {
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    if (chip != NULL) {
+      char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.ID");
+      if (name != NULL) {
+	psStringSubstitute(&newName, name, "{CHIP.ID}");
+      }
+    }
+  }
+  if (strstr (newName, "{CHIP.N}") != NULL) {
+    char *name = NULL;
+    if (view->chip < 0) {
+      psStringAppend (&name, "XX");
+    } else {
+      psStringAppend (&name, "%02d", view->chip);
+    }
+    psStringSubstitute(&newName, name, "{CHIP.N}");
+    psFree (name);
+  }
+  if (strstr (newName, "{CELL.NAME}") != NULL) {
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    if (cell != NULL) {
+      char *name = psMetadataLookupStr (NULL, cell->concepts, "CELL.NAME");
+      if (name != NULL) {
+	psStringSubstitute(&newName, name, "{CELL.NAME}");
+      }
+    }
+  }
+  if (strstr (newName, "{CELL.N}") != NULL) {
+    char *name = NULL;
+    if (view->cell < 0) {
+      psStringAppend (&name, "XX");
+    } else {
+      psStringAppend (&name, "%02d", view->cell);
+    }
+    psStringSubstitute(&newName, name, "{CELL.N}");
+  }
+  if (strstr (newName, "{EXTNAME}") != NULL) {
+    pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
+    if (hdu->extname && *hdu->extname) {
+      psStringSubstitute(&newName, hdu->extname, "{EXTNAME}");
+    }
+  }
+  if (strstr (newName, "{FILTER}") != NULL) {
+    if (fpa != NULL) {
+      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTER");
+      if (name && *name) {
+	psStringSubstitute(&newName, name, "{FILTER}");
+      }
+    }
+  }
+  if (strstr (newName, "{FILTER.ID}") != NULL) {
+    if (fpa != NULL) {
+      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTERID");
+      if (name && *name) {
+	psStringSubstitute(&newName, name, "{FILTER.ID}");
+      }
+    }
+  }
+  if (strstr (newName, "{CAMERA}") != NULL) {
+    if (fpa != NULL) {
+      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
+      if (name && *name) {
+	psStringSubstitute(&newName, name, "{CAMERA}");
+      }
+    }
+  }
+  if (strstr (newName, "{INSTRUMENT}") != NULL) {
+    if (fpa != NULL) {
+      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
+      if (name && *name) {
+	psStringSubstitute(&newName, name, "{INSTRUMENT}");
+      }
+    }
+  }
+  if (strstr (newName, "{DETECTOR}") != NULL) {
+    if (fpa != NULL) {
+      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.DETECTOR");
+      if (name && *name) {
+	psStringSubstitute(&newName, name, "{DETECTOR}");
+      }
+    }
+  }
+  if (strstr (newName, "{TELESCOPE}") != NULL) {
+    if (fpa != NULL) {
+      char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.TELESCOPE");
+      if (name && *name) {
+	psStringSubstitute(&newName, name, "{TELESCOPE}");
+      }
+    }
+  }
+  return newName;
+}
+
+// select the rule from the camera configuration, perform substitutions as needed
+psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view)
+{
+  PS_ASSERT_PTR_NON_NULL(rule, NULL);
+  PS_ASSERT_INT_POSITIVE(strlen(rule), NULL);
+  PS_ASSERT_PTR_NON_NULL(file, NULL);
+  PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+  psString newRule = NULL;            // Rule to pass on to pmFPANameFromRule
+  newRule = psStringCopy(rule);
+
+  if (strstr(newRule, "{OUTPUT}") != NULL) {
+    char *name = psMetadataLookupStr(NULL, file->names, "OUTPUT");
+    if (name) {
+      psStringSubstitute(&newRule, name, "{OUTPUT}");
+    }
+  }
+
+  psString newName = pmFPANameFromRule(newRule, file->fpa, view); // New name, to be returned
+  psFree(newRule);
+
+  return newName;
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view
+bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view)
+{
+  PS_ASSERT_PTR_NON_NULL(out, false);
+  PS_ASSERT_PTR_NON_NULL(in, false);
+  PS_ASSERT_PTR_NON_NULL(view, false);
+
+  // pmFPAWrite takes care of all PHUs as needed
+  if (view->chip == -1) {
+    pmFPACopy (out, in);
+    return true;
+  }
+  if (view->chip >= in->chips->n) {
+    psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
+    return false;
+  }
+  pmChip *inChip = in->chips->data[view->chip];
+  pmChip *outChip = out->chips->data[view->chip];
+
+  if (view->cell == -1) {
+    pmChipCopy (outChip, inChip);
+    return true;
+  }
+  if (view->cell >= inChip->cells->n) {
+    psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
+	    view->cell, inChip->cells->n);
+    return false;
+  }
+  pmCell *inCell = inChip->cells->data[view->cell];
+  pmCell *outCell = outChip->cells->data[view->cell];
+
+  if (view->readout == -1) {
+    pmCellCopy (outCell, inCell);
+    return true;
+  }
+  psError(PS_ERR_UNKNOWN, true, "Returning false");
+  return false;
+
+  // XXX add readout / segment equivalents
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view
+bool pmFPAfileCopyStructureView (pmFPA *out, const pmFPA *in, int xBin, int yBin, const pmFPAview *view)
+{
+  bool status;
+  PS_ASSERT_PTR_NON_NULL(out, false);
+  PS_ASSERT_PTR_NON_NULL(in, false);
+  PS_ASSERT_PTR_NON_NULL(view, false);
+
+  // XXX this should be smarter (ie, only copy concepts from the current chips)
+  // but such a call is needed, so re-copy stuff rather than no copy
+  pmFPACopyConcepts (out, in);
+
+  // pmFPAWrite takes care of all PHUs as needed
+  if (view->chip == -1) {
+    status = pmFPACopyStructure (out, in, xBin, yBin);
+    return status;
+  }
+  if (view->chip >= in->chips->n) {
+    psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
+    return false;
+  }
+  pmChip *inChip = in->chips->data[view->chip];
+  pmChip *outChip = out->chips->data[view->chip];
+
+  if (view->cell == -1) {
+    status = pmChipCopyStructure (outChip, inChip, xBin, yBin);
+    return status;
+  }
+  if (view->cell >= inChip->cells->n) {
+    psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
+	    view->cell, inChip->cells->n);
+    return false;
+  }
+  pmCell *inCell = inChip->cells->data[view->cell];
+  pmCell *outCell = outChip->cells->data[view->cell];
+
+  status = pmCellCopyStructure (outCell, inCell, xBin, yBin);
+  return status;
+}
+
+pmFPAfileType pmFPAfileTypeFromString(const char *type)
+{
+  PS_ASSERT_STRING_NON_EMPTY(type, PM_FPA_FILE_NONE);
+
+  if (!strcasecmp (type, "SX"))     {
+    return PM_FPA_FILE_SX;
+  }
+  if (!strcasecmp (type, "OBJ"))     {
+    return PM_FPA_FILE_OBJ;
+  }
+  if (!strcasecmp (type, "CMP"))     {
+    return PM_FPA_FILE_CMP;
+  }
+  if (!strcasecmp (type, "CMF"))     {
+    return PM_FPA_FILE_CMF;
+  }
+  if (!strcasecmp (type, "RAW"))     {
+    return PM_FPA_FILE_RAW;
+  }
+  if (!strcasecmp (type, "IMAGE"))     {
+    return PM_FPA_FILE_IMAGE;
+  }
+  if (!strcasecmp (type, "PSF"))     {
+    return PM_FPA_FILE_PSF;
+  }
+  if (!strcasecmp (type, "JPEG"))     {
+    return PM_FPA_FILE_JPEG;
+  }
+  if (!strcasecmp (type, "KAPA"))     {
+    return PM_FPA_FILE_KAPA;
+  }
+  if (!strcasecmp (type, "MASK"))     {
+    return PM_FPA_FILE_MASK;
+  }
+  if (!strcasecmp (type, "WEIGHT"))     {
+    return PM_FPA_FILE_WEIGHT;
+  }
+  if (!strcasecmp (type, "FRINGE")) {
+    return PM_FPA_FILE_FRINGE;
+  }
+  if (!strcasecmp (type, "HEADER"))     {
+    return PM_FPA_FILE_HEADER;
+  }
+  if (!strcasecmp (type, "ASTROM"))     {
+    return PM_FPA_FILE_ASTROM;
+  }
+
+  return PM_FPA_FILE_NONE;
+}
+
+char *pmFPAfileStringFromType(pmFPAfileType type)
+{
+  switch (type) {
+    case PM_FPA_FILE_SX:
+      return ("SX");
+    case PM_FPA_FILE_OBJ:
+      return ("OBJ");
+    case PM_FPA_FILE_CMP:
+      return ("CMP");
+    case PM_FPA_FILE_CMF:
+      return ("CMF");
+    case PM_FPA_FILE_RAW:
+      return ("RAW");
+    case PM_FPA_FILE_IMAGE:
+      return ("IMAGE");
+    case PM_FPA_FILE_PSF:
+      return ("PSF");
+    case PM_FPA_FILE_JPEG:
+      return ("JPEG");
+    case PM_FPA_FILE_KAPA:
+      return ("KAPA");
+    case PM_FPA_FILE_MASK:
+      return ("MASK");
+    case PM_FPA_FILE_WEIGHT:
+      return ("WEIGHT");
+    case PM_FPA_FILE_FRINGE:
+      return ("FRINGE");
+    case PM_FPA_FILE_HEADER:
+      return ("HEADER");
+    case PM_FPA_FILE_ASTROM:
+      return ("ASTROM");
+    default:
+      return ("NONE");
+  }
+  return ("NONE");
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfile.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfile.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfile.h	(revision 22290)
@@ -0,0 +1,132 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.25 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-27 02:07:16 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FILE_H
+#define PM_FPA_FILE_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+typedef enum {
+    PM_FPA_BEFORE,
+    PM_FPA_AFTER,
+} pmFPAfilePlace;
+
+typedef enum {
+    PM_FPA_FILE_NONE,
+    PM_FPA_FILE_SX,
+    PM_FPA_FILE_OBJ,
+    PM_FPA_FILE_CMP,
+    PM_FPA_FILE_CMF,
+    PM_FPA_FILE_RAW,
+    PM_FPA_FILE_IMAGE,
+    PM_FPA_FILE_PSF,
+    PM_FPA_FILE_JPEG,
+    PM_FPA_FILE_KAPA,
+    PM_FPA_FILE_MASK,
+    PM_FPA_FILE_WEIGHT,
+    PM_FPA_FILE_FRINGE,
+    PM_FPA_FILE_HEADER,
+    PM_FPA_FILE_ASTROM,
+} pmFPAfileType;
+
+typedef enum {
+    PM_FPA_MODE_NONE,
+    PM_FPA_MODE_READ,
+    PM_FPA_MODE_WRITE,
+    PM_FPA_MODE_INTERNAL,
+    PM_FPA_MODE_REFERENCE,
+} pmFPAfileMode;
+
+typedef enum {
+    PM_FPA_STATE_OPEN     = 0x01,
+    PM_FPA_STATE_CLOSED   = 0x02,
+    PM_FPA_STATE_INACTIVE = 0x04,
+} pmFPAfileState;
+
+typedef struct {
+    pmFPAfileMode mode;                 // is this file read, written, or only used internally?
+    pmFPAfileType type;                 // what type of data is read from / written to disk?
+    pmFPAfileState state;               // have we opened the file, etc?
+
+    pmFPALevel fileLevel;               // what level in the FPA hierarchy represents a unique file?
+    pmFPALevel dataLevel;               // at what level do we read/write the data segment? (request by user)
+    pmFPALevel freeLevel;               // at what level do we free the data segment? (set by program)
+    pmFPALevel mosaicLevel;             // at what level is the mosaic?
+
+    pmFPA *fpa;                         // for I/O files, we carry a pointer to the complete fpa
+    psFits *fits;                       // for I/O files of fits type (IMAGE, CMP, CMF) we carry a file handle
+    psFitsCompression *compression;     // Compression for FITS images
+    int bitpix;                         // Bits per pixel for output
+    psFitsFloat floatType;              // Custom floating-point
+
+    bool wrote_phu;                     // have we written a PHU for this file?
+    psMetadata *header;                 // pointer (view) to the current hdu header
+
+    pmReadout *readout;                 // for internal files, we only carry a single readout
+
+    psMetadata *names;                  // filenames supplied by the cmdline or detdb are saved here
+
+    char *filerule;                     // rule for constructing a filename when needed
+    char *filesrc;                      // rule to find file in pmFPAfile->names list
+
+    char *name;                         // the name of the rule (useful for debugging / tracing)
+    char *filename;                     // the current name of an active file
+    char *extname;                      // the current name of an active file extension
+
+    pmDetrendSelectResults *detrend;    // Detrend information, from pmDetrendSelect
+
+    bool save;                          // Should the file be saved?
+
+    // the following elements are used for WRITE-mode IMAGE-type pmFPAfiles to inform
+    // the creation of a new image based on an existing image
+    pmFPA *src;                         // if an output FPA, inherit from this FPA
+    int xBin;                           // desired binning in x direction
+    int yBin;                           // desired binning in y direction
+
+    psMetadata *camera;                 // Camera configuration
+    psString cameraName;                // Name of the camera
+    psMetadata *format;                 // Camera format
+    psString formatName;                // name of the camera format
+} pmFPAfile;
+
+// allocate an empty pmFPAfile structure
+pmFPAfile *pmFPAfileAlloc ();
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name);
+
+// select the cell from the named pmFPAfile; if the named file does not exist,
+pmCell *pmFPAfileThisCell (psMetadata *files, const pmFPAview *view, const char *name);
+
+// select the chip from the named pmFPAfile; if the named file does not exist,
+pmChip *pmFPAfileThisChip (psMetadata *files, const pmFPAview *view, const char *name);
+
+// add the specified filename info (value) to the files of the given mode using the given reference name
+bool pmFPAfileAddFileNames (psMetadata *files, char *name, char *value, int mode);
+
+// convert the rule to a name based on the current view
+psString pmFPANameFromRule(const char *rule, const pmFPA *fpa, const pmFPAview *view);
+
+// convert the rule to a name based on the current view
+psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view);
+
+bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view);
+
+bool pmFPAfileCopyStructureView (pmFPA *out, const pmFPA *in, int xBin, int yBin, const pmFPAview *view);
+
+// Return the file type enum from a string
+pmFPAfileType pmFPAfileTypeFromString(const char *type);
+
+// Return the file type as a string
+char *pmFPAfileStringFromType(pmFPAfileType type);
+
+/// @}
+# endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileDefine.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileDefine.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileDefine.c	(revision 22290)
@@ -0,0 +1,1276 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmErrorCodes.h"
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfile.h"
+#include "pmFPAConstruct.h"
+
+#include "pmConcepts.h"
+
+// Parse an option from a metadata, returning the appropriate integer value
+static int parseOptionInt(const psMetadata *md, // Metadata containing the option
+                          const char *name, // Option name
+                          const char *source, // Description of source, for warning messages
+                          int defaultValue // Default value
+                          )
+{
+    psMetadataItem *item = psMetadataLookup(md, name); // Item with the value of interest
+    if (!item) {
+        psWarning("Unable to find value for %s in %s --- set to %d.", name, source, defaultValue);
+        return defaultValue;
+    }
+    int value = psMetadataItemParseS32(item); // Value of interst
+    return value;
+}
+
+
+// define an input-type pmFPAfile, bind to the optional fpa if supplied
+pmFPAfile *pmFPAfileDefineInput(const pmConfig *config, pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    bool status;
+    char *type;
+
+    const psMetadata *camera = (fpa ? fpa->camera : config->camera); // Camera configuration for this file
+    psMetadata *data = pmConfigFileRule(config, camera, name); // File rule
+    if (!data) {
+        psError(PS_ERR_IO, true, "Can't find file rule %s!", name);
+        return NULL;
+    }
+
+    pmFPAfile *file = pmFPAfileAlloc ();
+
+    // save the name of this pmFPAfile
+    file->name = psStringCopy (name);
+
+    file->filerule = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.RULE"));
+
+    type = psMetadataLookupStr (&status, data, "FILE.TYPE");
+    file->type = pmFPAfileTypeFromString(type);
+    if (file->type == PM_FPA_FILE_NONE) {
+        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        return NULL;
+    }
+
+    file->mode = PM_FPA_MODE_READ;
+    file->fileLevel = PM_FPA_LEVEL_NONE; // the fileLevel depends on the input data
+
+    file->dataLevel = pmFPALevelFromName(psMetadataLookupStr (&status, data, "DATA.LEVEL"));
+    if (file->dataLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        return NULL;
+    }
+    // default is to free the data after use (after written out)
+    // this can be overridden for pmFPAfiles used as carriers as well
+    file->freeLevel = file->dataLevel;
+
+    if (fpa) {
+        file->fpa = psMemIncrRefCounter(fpa);
+        file->camera = psMemIncrRefCounter((psMetadata *)fpa->camera);
+        file->cameraName = psMemIncrRefCounter(config->cameraName); // XXX Is this the correct thing to do?
+    } else {
+        file->camera = psMemIncrRefCounter(config->camera);
+        file->cameraName = psMemIncrRefCounter(config->cameraName);
+    }
+
+    // XXX ppImage and similar require the added file to be unique
+    // XXX ppFocus wants to override the selection with the new selection
+    // XXX require programs like ppFocus to remove existing files by hand
+    if (!psMetadataAddPtr (config->files, PS_LIST_TAIL, name,
+                           PS_DATA_UNKNOWN | PS_META_DUPLICATE_OK, "", file)) {
+        psError (PS_ERR_IO, false, "could not add %s to config files", name);
+        return NULL;
+    }
+    psFree (file);
+    return (file);
+}
+
+// Define an output pmFPAfile
+pmFPAfile *pmFPAfileDefineOutputForFormat(const pmConfig *config, // Configuration
+                                          pmFPA *fpa, // Optional FPA to bind
+                                          const char *name, // Name of file rule
+                                          psString cameraName, // Name of camera configuration to use
+                                          psString formatName // Name of camera format to use
+    )
+{
+    bool status;
+
+    // Use the camera we were told to, the camera of the provided FPA, or default to the default camera
+    psMetadata *camera;                 // Camera configuration
+    if (!cameraName || strlen(cameraName) == 0) {
+        if (fpa && fpa->camera) {
+            camera = (psMetadata*)fpa->camera; // Casting away const, so I can put it in the file
+        } else {
+            camera = config->camera;
+            cameraName = config->cameraName;
+        }
+    } else {
+        bool mdok;                      // Status of MD lookup
+        psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->site, "CAMERAS"); // Known cameras
+        if (!mdok || !cameras) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the site configuration.\n");
+            return NULL;
+        }
+        camera = psMetadataLookupMetadata(&mdok, cameras, cameraName); // Camera configuration of interest
+        if (!mdok || !camera) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find automatically generated "
+                    "camera configuration %s in site configuration.\n", cameraName);
+            return NULL;
+        }
+
+        if (fpa && fpa->camera && fpa->camera != camera) {
+            psAbort("Camera of bound FPA is not the requested camera --- there is an inconsistency!");
+        }
+    }
+
+    psMetadata *data = pmConfigFileRule(config, camera, name); // File rule
+    if (!data) {
+        psError(PS_ERR_IO, true, "Can't find file rule %s!", name);
+        return NULL;
+    }
+
+    pmFPAfile *file = pmFPAfileAlloc();
+
+    // save the name of this pmFPAfile
+    file->name = psStringCopy(name);
+
+    file->filerule = psMemIncrRefCounter(psMetadataLookupStr(&status, data, "FILENAME.RULE"));
+
+    const char *type = psMetadataLookupStr(&status, data, "FILE.TYPE");
+    file->type = pmFPAfileTypeFromString(type);
+    if (file->type == PM_FPA_FILE_NONE) {
+        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+
+    file->mode = PM_FPA_MODE_WRITE;
+    file->save = false;
+
+    file->camera = psMemIncrRefCounter(camera);
+    file->cameraName = psMemIncrRefCounter(cameraName);
+
+    // XXX this seems a bit of a hack: use the cameraName to determine the mosaic level...
+    # if (0)
+    if (cameraName) {
+        if (!strcmp(cameraName + strlen(cameraName) - 5, "-CHIP")) {
+            file->mosaicLevel = PM_FPA_LEVEL_CHIP;
+        }
+        if (!strcmp(cameraName + strlen(cameraName) - 5, "-FPA")) {
+            file->mosaicLevel = PM_FPA_LEVEL_FPA;
+        }
+    }
+    # endif
+
+    // Use the format we were told to, the format specified in the file rule, or default to the default format
+    if (!formatName || strlen(formatName) == 0) {
+        // select the format list from the selected camera
+        formatName = psMetadataLookupStr(&status, data, "FILE.FORMAT");
+        if (!formatName || strcmp(formatName, "NONE") == 0) {
+            // Try to get by with the default
+            formatName = config->formatName;
+        }
+    }
+    psMetadata *formats = psMetadataLookupMetadata(&status, file->camera, "FORMATS"); // List of formats
+    psMetadata *format = psMetadataLookupMetadata(&status, formats, formatName); // Camera format to use
+    if (!format) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find format %s for file %s.\n",
+                formatName, file->name);
+        psFree(file);
+        return NULL;
+    }
+    file->format = psMemIncrRefCounter(format);
+    file->formatName = psStringCopy(formatName);
+
+    if (fpa) {
+        file->fpa = psMemIncrRefCounter(fpa);
+    } else {
+        file->fpa = pmFPAConstruct(file->camera);
+    }
+
+    // Get FITS output scheme
+    const char *fitsType = psMetadataLookupStr(&status, data, "FITS.TYPE"); // Name of FITS scheme to use
+    if (fitsType && strcasecmp(fitsType, "NONE") != 0) {
+        psMetadata *fitsTypes = psMetadataLookupMetadata(&status, camera, "FITS"); // The FITS schemes
+        if (!fitsTypes) {
+            psWarning("Unable to find FITS in camera configuration --- compression disabled.");
+            goto COMPRESSION_DONE;
+        }
+        psMetadata *scheme = psMetadataLookupMetadata(NULL, fitsTypes, fitsType); // FITS scheme
+        if (!scheme) {
+            psWarning("Unable to find %s in FITS in camera configuration --- compression disabled.",
+                      fitsType);
+            goto COMPRESSION_DONE;
+        }
+        const char *typeString = psMetadataLookupStr(NULL, scheme, "COMP"); // Compression type
+        if (!typeString || strlen(typeString) == 0) {
+            psWarning("Can't find COMP in FITS scheme %s --- compression disabled.", fitsType);
+            goto COMPRESSION_DONE;
+        }
+        psFitsCompressionType type = psFitsCompressionTypeFromString(typeString); // Compression type enum
+
+        psString source = NULL;         // Source of options
+        psStringAppend(&source, "%s in FITS in camera %s", fitsType, cameraName);
+        file->bitpix = parseOptionInt(scheme, "BITPIX", source, 0); // Bits per pixel
+
+        // Custom floating-point
+        const char *floatName = psMetadataLookupStr(NULL, scheme, "FLOAT"); // Name of custom floating-point
+        if (floatName) {
+            psString fullName = NULL;   // Full name of custom floating-point
+            psStringAppend(&fullName, "FLOAT_%s", floatName);
+            file->floatType = psFitsFloatTypeFromString(fullName);
+            psFree(fullName);
+        }
+
+        psVector *tile = psVectorAlloc(3, PS_TYPE_S32); // Tile sizes
+        tile->data.S32[0] = parseOptionInt(scheme, "TILE.X", source, 0); // Tiling in x
+        tile->data.S32[1] = parseOptionInt(scheme, "TILE.Y", source, 1); // Tiling in y
+        tile->data.S32[2] = parseOptionInt(scheme, "TILE.Z", source, 1); // Tiling in z
+        int noise = parseOptionInt(scheme, "NOISE", source, 16); // Noise bits
+        int hscale = parseOptionInt(scheme, "HSCALE", source, 0); // Scaling for HCOMPRESS
+        int hsmooth = parseOptionInt(scheme, "HSMOOTH", source, 0); // Smoothing for HCOMPRESS
+        psFree(source);
+
+        file->compression = psFitsCompressionAlloc(type, tile, noise, hscale, hsmooth);
+        psFree(tile);
+    }
+COMPRESSION_DONE:
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+
+    file->dataLevel = pmFPALevelFromName(psMetadataLookupStr(&status, data, "DATA.LEVEL"));
+    if (file->dataLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+    // default is to free the data after use (after written out)
+    // this can be overridden for pmFPAfiles used as carriers as well
+    file->freeLevel = file->dataLevel;
+    file->fileLevel = PS_MIN (file->fileLevel, file->dataLevel);
+
+    // XXX the file/data/free level must be consistent with the reference fpa (but since we
+    // don't have access to its pmFPAfile, we cannot enforce this here...
+
+    pmFPALevel extLevel = pmFPAExtensionsLevel(format); // Level for extensions
+    if (extLevel != PM_FPA_LEVEL_NONE) {
+        if (extLevel < file->dataLevel) {
+            psWarning("Level for extensions is higher than desired data level --- adjusting.\n");
+            file->dataLevel = extLevel;
+        }
+        if (extLevel < file->freeLevel) {
+            psWarning("Level for extensions is higher than desired free level --- adjusting.\n");
+            file->freeLevel = extLevel;
+        }
+    } else {
+        // if we do not have extensions in the file, we are forced to write out at the file level
+        file->dataLevel = file->fileLevel;
+        file->freeLevel = file->fileLevel;
+    }
+
+    psTrace ("psModules.camera", 5, "file: %s, format: %s, fileLevel: %s, extLevel: %s, dataLevel: %s, freeLevel: %s\n",
+             file->name, file->formatName, pmFPALevelToName (file->fileLevel), pmFPALevelToName(extLevel), pmFPALevelToName (file->dataLevel), pmFPALevelToName (file->freeLevel));
+
+    // add argument-supplied OUTPUT name to this file
+    char *outname = psMetadataLookupStr(&status, config->arguments, "OUTPUT");
+    psMetadataAddStr(file->names, PS_LIST_TAIL, "OUTPUT", PS_META_NO_REPLACE, "", outname);
+
+    // place the resulting file in the config system
+    psMetadataAddPtr (config->files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+    psFree (file); // we free this copy of file, but 'files' still has a copy
+    return (file); // the returned value is a view into the version on 'files'
+}
+
+// define a pmFPAfile, bind to the optional fpa if supplied
+pmFPAfile *pmFPAfileDefineOutput(const pmConfig *config, pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    return pmFPAfileDefineOutputForFormat(config, fpa, name, NULL, NULL);
+}
+
+// define a pmFPAfile, bind to the optional file if supplied
+pmFPAfile *pmFPAfileDefineOutputFromFile(const pmConfig *config, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    char *cameraName = NULL, *formatName = NULL; // Name of camera and format
+    pmFPA *fpa = NULL;                  // FPA for file
+    if (file) {
+        cameraName = file->cameraName;
+        formatName = file->formatName;
+        fpa = file->fpa;
+    }
+
+    return pmFPAfileDefineOutputForFormat(config, fpa, name, cameraName, formatName);
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (must represent a single FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineFromArgs(bool *success, pmConfig *config, const char *filename, const char *argname)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+
+    // use success to identify valid exit conditions (as opposed to 'argument not supplied')
+    if (success) *success = false;
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        if (success) *success = true;
+        return NULL;
+    }
+    if (infiles->n < 1) {
+        psError(PS_ERR_IO, false, "Found n == %ld files in %s in arguments\n", infiles->n, argname);
+        return NULL;
+    }
+
+    // this function is implicitly an INPUT operation: do not create the file
+    psString realName = pmConfigConvertFilename (infiles->data[0], config, false);
+    if (!realName) {
+        psError(PS_ERR_IO, false, "Failed to convert file name %s\n", (char *) infiles->data[0]);
+        return NULL;
+    }
+
+    // load the header of the first image
+    // EXTWORD (fits->extword) is not relevant to the PHU
+    fits = psFitsOpen (realName, "r");
+    if (!fits) {
+        psError(PS_ERR_IO, false, "Failed to open file %s\n", realName);
+        psFree (realName);
+        return NULL;
+    }
+    phu = psFitsReadHeader (NULL, fits);
+    if (!phu) {
+        psError(PS_ERR_IO, false, "Failed to read file header %s\n", realName);
+        psFree (realName);
+        return NULL;
+    }
+    psFitsClose(fits);
+
+    // determine the current format from the header
+    // determine camera if not specified already
+    format = pmConfigCameraFormatFromHeader (config, phu, true);
+    if (!format) {
+        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", realName);
+        psFree(phu);
+        psFree (realName);
+        return NULL;
+    }
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (config->camera);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA from %s", realName);
+        psFree (realName);
+        return NULL;
+    }
+    psFree (realName);
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    file = pmFPAfileDefineInput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        return NULL;
+    }
+    psFree (format);
+    file->format = psMemIncrRefCounter(format);
+    file->formatName = psStringCopy(config->formatName);
+
+    // adjust the rules to identify these files in the file->names data
+    psFree (file->filerule);
+    file->filerule = psStringCopy ("@FILES");
+    file->filesrc = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", file->name);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        psFree(file);
+        return NULL;
+    }
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    for (int i = 0; i < infiles->n; i++) {
+        if (i > 0) {
+            // this function is implicitly an INPUT operation: do not create the file
+            psString realName = pmConfigConvertFilename (infiles->data[i], config, false);
+            if (!realName) {
+                psError(PS_ERR_IO, false, "Failed to convert file name %s", (char *) infiles->data[i]);
+                return NULL;
+            }
+            // EXTWORD (fits->extword) is not relevant to the PHU
+            fits = psFitsOpen (realName, "r");
+            if (!fits) {
+                psError(PS_ERR_IO, false, "Failed to open file %s\n", realName);
+                psFree (realName);
+                return NULL;
+            }
+            phu = psFitsReadHeader (NULL, fits);
+            if (!phu) {
+                psError(PS_ERR_IO, false, "Failed to read file header %s", realName);
+                psFree (realName);
+                psFitsClose (fits);
+                return NULL;
+            }
+            bool valid = false;
+            if (!pmConfigValidateCameraFormat (&valid, format, phu)) {
+                psError (PS_ERR_UNKNOWN, false, "Error in config scripts\n");
+                psFree (realName);
+                psFitsClose (fits);
+                return NULL;
+            }
+            if (!valid) {
+                psError(PS_ERR_IO, false, "file %s is not from the required camera", realName);
+                psFree (realName);
+                psFitsClose (fits);
+                return NULL;
+            }
+            psFree(realName);
+            psFitsClose (fits);
+        }
+
+        // set the view to the corresponding entry for this phu
+        pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+        if (!view) {
+            psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+            psFree(phu);
+            psFree (fpa);
+            psFree (format);
+            return NULL;
+        }
+
+        // associate the filename with the FPA element
+        char *name = pmFPAfileNameFromRule (file->filesrc, file, view);
+
+        // save the name association in the pmFPAfile structure
+        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+        psFree (view);
+        psFree (name);
+        psFree (phu);
+    }
+    psFree (fpa);
+    if (success) *success = true;
+
+    return file;
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (must represent a single FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileBindFromArgs (bool *success, pmFPAfile *input, pmConfig *config, const char *filename, const char *argname)
+{
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+
+    // use success to identify valid exit conditions (as opposed to 'argument not supplied')
+    if (success) *success = false;
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        // this is not an error: this just means no matching argument was supplied
+        if (success) *success = true;
+        return NULL;
+    }
+    if (infiles->n < 1) {
+        psError(PS_ERR_IO, false, "Found n == %ld files in %s in arguments\n", infiles->n, argname);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    file = pmFPAfileDefineInput (config, input->fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        return NULL;
+    }
+
+    // set derived values
+    file->fileLevel = input->fileLevel;
+
+    // define the rule to identify these files in the file->names data
+    psFree (file->filerule);
+    psFree (file->filesrc);
+    file->filerule = psStringCopy ("@FILES");
+    file->filesrc = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    psMetadata *format = NULL;
+    for (int i = 0; i < infiles->n; i++) {
+        // this function is implicitly an INPUT operation: do not create the file
+        psString realName = pmConfigConvertFilename (infiles->data[i], config, false);
+        if (!realName) {
+            psError(PS_ERR_IO, false, "Failed to convert file name %s", (char *) infiles->data[i]);
+            return NULL;
+        }
+        // EXTWORD (fits->extword) is not relevant to the PHU
+        fits = psFitsOpen (realName, "r");
+        if (!fits) {
+            psError(PS_ERR_IO, false, "Failed to open file %s\n", realName);
+            psFree (realName);
+            return NULL;
+        }
+        phu = psFitsReadHeader (NULL, fits);
+        if (!phu) {
+            psError(PS_ERR_IO, false, "Failed to read file header %s", realName);
+            psFree (realName);
+            psFitsClose (fits);
+            return NULL;
+        }
+
+        if (!format) {
+            format = pmConfigCameraFormatFromHeader(config, phu, true);
+            if (!format) {
+                psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", realName);
+                psFree(phu);
+                psFree(realName);
+                psFitsClose(fits);
+                psFree(file);
+                return NULL;
+            }
+        } else {
+            bool valid = false;
+            if (!pmConfigValidateCameraFormat(&valid, format, phu)) {
+                psError(PS_ERR_UNKNOWN, false, "Error in config scripts\n");
+                psFree(realName);
+                psFree(file);
+                psFitsClose(fits);
+                return NULL;
+            }
+            if (!valid) {
+                psError(PS_ERR_IO, false, "specified data file %s does not match format of supplied INPUT\n",
+                        realName);
+                psFree(realName);
+                psFree(file);
+                psFitsClose(fits);
+                return NULL;
+            }
+        }
+
+        psFree(realName);
+        psFitsClose(fits);
+
+        // set the view to the corresponding entry for this phu
+        pmFPAview *view = pmFPAIdentifySourceFromHeader (input->fpa, phu, format);
+        if (!view) {
+            psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+            psFree(phu);
+            return NULL;
+        }
+
+        // associate the filename with the FPA element
+        char *name = pmFPAfileNameFromRule(file->filesrc, file, view);
+
+        // save the name association in the pmFPAfile structure
+        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+        psFree (view);
+        psFree (name);
+        psFree (phu);
+    }
+    psFree(file->format);
+    file->format = format;
+    file->formatName = psStringCopy(config->formatName);
+
+    if (success) *success = true;
+    return file;
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (each represents the same FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filenames and bind them to the FPAs
+// save the pmFPAfiles on config->files
+// return the pmFPAfiles (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineSingleFromArgs (bool *success, pmConfig *config, const char *filename,
+        const char *argname, int entry)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+
+    if (success) *success = false;
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        psTrace("psModules.camera", 5, "Failed to find %s in argument list", argname);
+        if (success) *success = true;
+        return NULL;
+    }
+    if (infiles->n <= entry) {
+        psError(PS_ERR_IO, false, "only %ld files in %s in argument, entry %d requested\n", infiles->n, argname, entry);
+        return NULL;
+    }
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    // EXTWORD (fits->extword) is not relevant to the PHU
+    fits = psFitsOpen (infiles->data[entry], "r");
+    phu = psFitsReadHeader (NULL, fits);
+    psFitsClose (fits);
+
+    // on first call to this function, config->camera is not set.
+    // later calls will give an error if the cameras do not match
+    format = pmConfigCameraFormatFromHeader (config, phu, true);
+    if (!format) {
+        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", (char *)infiles->data[0]);
+        psFree(phu);
+        return NULL;
+    }
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (config->camera);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA from %s", (char *)infiles->data[0]);
+        psFree(phu);
+        psFree(format);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    // we need a variable name here... (but in filerule)
+    file = pmFPAfileDefineInput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        return NULL;
+    }
+    psFree (file->format);
+    file->format = format;
+    file->formatName = psStringCopy(config->formatName);
+
+    // adjust the rules to identify these files in the file->names data
+    psFree (file->filerule);
+    psFree (file->filesrc);
+    file->filerule = psStringCopy ("@FILES");
+    file->filesrc = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", file->name);
+        psFree(phu);
+        psFree(fpa);
+        psFree(file);
+        psFree(format);
+        return NULL;
+    }
+
+    // set the view to the corresponding entry for this phu
+    pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+    if (!view) {
+        psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+        psFree(phu);
+        psFree(fpa);
+        psFree(file);
+        psFree(format);
+        return NULL;
+    }
+
+    // associate the filename with the FPA element
+    char *name = pmFPAfileNameFromRule (file->filesrc, file, view);
+
+    // save the name association in the pmFPAfile structure
+    psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[entry]);
+
+    psFree(phu);
+    psFree(fpa);
+    psFree(view);
+    psFree(name);
+    psFree(format);
+
+    if (success) *success = true;
+    return file;
+}
+
+// define the named pmFPAfile from the camera->config
+// only valid for pmFPAfile->mode = READ
+pmFPAfile *pmFPAfileDefineFromConf (bool *success, const pmConfig *config, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    if (success) *success = false;
+
+    // a camera config is needed (as source of file rule)
+    if (config->camera == NULL) {
+        psError(PS_ERR_IO, true, "camera is not defined");
+        return NULL;
+    }
+
+    // build the template fpa, set up the basic view
+    pmFPA *fpa = pmFPAConstruct (config->camera);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    pmFPAfile *file = pmFPAfileDefineInput (config, fpa, filename);
+    psFree (fpa);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    // image names may not come from file->names
+    if (!strcasecmp (file->filerule, "@FILES")) {
+        psError(PS_ERR_IO, true, "supplied filerule uses illegal value @FILES");
+        // XXX remove the file from config->files
+        psFree(file);
+        return NULL;
+    }
+
+    // image names may come from the detrend database
+    if (!strcasecmp (file->filerule, "@DETDB")) {
+        psTrace ("pmFPAfile", 5, "requiring use of detrend database source\n");
+        // don't free the file here: it is left on config->files
+        // to be used optionally by pmFPAfileDefineFromDetDB (or others)
+        if (success) *success = true;
+        return NULL;
+    }
+
+    // Prepend the global path to the file rule
+    // this function is implicitly an INPUT operation: do not create the file
+    psString tmpName = pmConfigConvertFilename (file->filerule, config, false);
+    psFree (file->filerule);
+    file->filerule = tmpName;
+
+    if (success) *success = true;
+
+    return file;
+}
+
+// construct an FPA based on the supplied config->camera
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineFromDetDB (bool *success, const pmConfig *config, const char *filename,
+                                     pmFPA *input, pmDetrendType type)
+{
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    pmFPAfile *file = NULL;
+
+    if (success) *success = false;
+
+    // a camera config is needed (as source of file rule)
+    if (config->camera == NULL) {
+        psError(PS_ERR_IO, true, "camera is not defined");
+        return NULL;
+    }
+    // a camera config is needed (as source of file rule)
+    if (config->cameraName == NULL) {
+        psAbort("camera defined but not cameraName!");
+    }
+
+    // find or define a pmFPAfile with this name
+    file = psMetadataLookupPtr (NULL, config->files, filename);
+    if (!file) {
+        // build the template fpa, set up the basic view
+        fpa = pmFPAConstruct (config->camera);
+        if (!fpa) {
+            psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+            return NULL;
+        }
+        // load the given filerule (from config->camera) and bind it to the fpa
+        // the returned file is just a view to the entry on config->files
+        file = pmFPAfileDefineInput (config, fpa, filename);
+        if (!file) {
+            psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+            psFree (fpa);
+            return NULL;
+        }
+    }
+
+    // we are constructing a detselect command of the form:
+    //   detselect -search -inst (camera) -type (type) -time (time) [others]
+    // camera, type, and time are derived from pmFPA *input, other options are
+    // added if specified for the particular detrend type by the DETREND.CONSTRAINTS
+    // note that the filter-dependent choices are set for ppImage in ppImageParseCamera
+    // XXX make all of the detrend constraints explicit in DETREND.CONSTRAINTS?
+
+    // Get the time from FPA.TIME
+    psTime *time = psMetadataLookupPtr(NULL, input->concepts, "FPA.TIME");
+    if (time->sec == 0 && time->nsec == 0) {
+        psLogMsg ("psModules.camera", PS_LOG_WARN, "FPA.TIME has not been set.\n");
+    }
+
+    // XXX careful about this: is this set correctly in the camera.config files?
+    char *instrument = psMetadataLookupStr(NULL, input->concepts, "FPA.INSTRUMENT");
+    pmDetrendSelectOptions *options = pmDetrendSelectOptionsAlloc(instrument, *time, type);
+
+    // add additional constraints based on the type defined in the PPIMAGE recipe
+    // XXX use PPIMAGE or DETREND for the recipe name?
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, "PPIMAGE");
+    if (!status) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "PPIMAGE recipe not found.");
+        psFree(options);
+        psFree(fpa);
+        return false;
+    }
+    psMetadata *detConstraints = psMetadataLookupPtr (&status, recipe, "DETREND.CONSTRAINTS");
+    if (!status) {
+        psWarning("DETREND.CONSTRAINTS not found --- no constraints will be applied.");
+        goto DETREND_SELECT;
+    }
+
+    psString typeName = pmDetrendTypeToString (type);
+    psMetadata *constraints = psMetadataLookupPtr (&status, detConstraints, typeName);
+    if (!status) {
+        psWarning("DETREND.CONSTRAINTS for type %s not found --- no contraints will be applied.", typeName);
+        psFree(typeName);
+        goto DETREND_SELECT;
+    }
+    psFree(typeName);
+
+    // loop over the constraints and include in the detselect options
+    psMetadataIterator *iter = psMetadataIteratorAlloc (constraints, PS_LIST_HEAD, NULL);
+    psMetadataItem *item = NULL;
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        if (item->type != PS_DATA_STRING) {
+            psWarning("Invalid type for DETREND.CONSTRAINT element %s --- ignoring constraint", item->name);
+            continue;
+        }
+        char *option  = item->name;     // item->name must correspond to a valid detselect option
+        char *concept = item->data.V;
+
+        // these items refer to the corresponding values for the input image
+        // (ie, -filter input:filter or -exptime input:exptime)
+        if (!strcasecmp (option, "filter")) {
+            options->filter = psMetadataLookupPtr (&status, input->concepts, concept);
+            psMemIncrRefCounter (options->filter);
+            if (!status)
+                psAbort("failed to find filter (concept %s)", concept);
+        } else if (!strcasecmp (option, "exptime")) {
+            options->exptime = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->exptimeSet = true;
+            if (!status)
+                psAbort("exptime not found (concept %s)", concept);
+        } else if (!strcasecmp (option, "airmass")) {
+            options->airmass = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->airmassSet = true;
+            if (!status)
+                psAbort("airmass not found (concept %s)", concept);
+        } else if (!strcasecmp (option, "dettemp")) {
+            options->dettemp = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->dettempSet = true;
+            if (!status)
+                psAbort("dettemp not found (concept %s)", concept);
+        } else if (!strcasecmp (option, "twilight")) {
+            options->twilight = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->twilightSet = true;
+            if (!status)
+                psAbort("twilight not found (concept %s)", concept);
+        }
+
+        // the version is applied literally
+        if (!strcasecmp (option, "version")) {
+            options->version = psMemIncrRefCounter (concept);
+        }
+        // we can override the detrend database dettype if desired
+        // ie, use DOMEFLAT for type FLAT
+        // the dettype string is applied literally
+        if (!strcasecmp (option, "dettype")) {
+            options->dettype = psMemIncrRefCounter (concept);
+        }
+    }
+    psFree(iter);
+
+DETREND_SELECT:
+    {
+        // search for existing detrend data (detID)
+        pmDetrendSelectResults *results = pmDetrendSelect (options, config);
+        if (!results) {
+            psError (PS_ERR_IO, false, "no matching detrend data");
+            return NULL;
+        }
+        file->detrend = results;
+	file->fileLevel = pmFPALevelFromName(results->level);
+	if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+            psError (PS_ERR_IO, false, "invalid file level for selected detrend data");
+            return NULL;
+        }
+    }
+
+    psFree (options);
+
+    if (success) *success = true;
+    return file;
+}
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+pmFPAfile *pmFPAfileDefineFromFPA (const pmConfig *config, pmFPA *src, int xBin, int yBin, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(src, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    pmFPA *fpa = pmFPAConstruct(src->camera);
+    // XXX should this use DefineOutputForFormat?
+    pmFPAfile *file = pmFPAfileDefineOutput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+    file->src = psMemIncrRefCounter(src); // inherit output elements from this source pmFPA
+    file->xBin = xBin;
+    file->yBin = yBin;
+    psFree (fpa);
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineFromFile(const pmConfig *config, pmFPAfile *src, int xBin, int yBin,
+                                   const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(src, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    pmFPAfile *file = pmFPAfileDefineOutputForFormat(config, NULL, filename, src->cameraName,
+                                                     src->formatName);
+    file->src = psMemIncrRefCounter(src->fpa); // inherit output elements from this source pmFPA
+    file->xBin = xBin;
+    file->yBin = yBin;
+
+    // inherit the concepts from the src fpa:
+    pmFPACopyConcepts(file->fpa, file->src);
+    
+    return file;
+}
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+pmFPAfile *pmFPAfileDefineNewCamera (const pmConfig *config, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    pmFPAfile *file = pmFPAfileDefineOutput (config, NULL, filename);
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+    if (!file->camera) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s does not define a new camera\n", filename);
+        return NULL;
+    }
+    file->fpa = pmFPAConstruct(file->camera);
+
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineSkycell(const pmConfig *config, pmFPA *fpa, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->cameraName, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->formatName, NULL);
+
+    pmFPAfile *file;                    // The new file
+
+    if (config->cameraName[0] == '_' &&
+        strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0) {
+        // The input camera is already a skycell
+        file = pmFPAfileDefineOutputForFormat(config, fpa, filename, config->cameraName, "SKYCELL");
+    } else {
+        psString cameraName = NULL;         // Name of the old camera configuration
+        if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 5, "-CHIP") == 0) {
+            cameraName = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 6);
+        } else if (config->cameraName[0] == '_' &&
+                   strcmp(config->cameraName + strlen(config->cameraName) - 4 , "-FPA") == 0) {
+            cameraName = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 5);
+        } else {
+            cameraName = psMemIncrRefCounter(config->cameraName);
+        }
+        psString newCameraName = NULL;  // Name of the new (automatically-generated) camera configuration
+        psStringAppend(&newCameraName, "_%s-SKYCELL", cameraName);
+        file = pmFPAfileDefineOutputForFormat(config, fpa, filename, newCameraName, "SKYCELL");
+        psFree(cameraName);
+        psFree(newCameraName);
+    }
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    // Ensure everything is written out at the appropriate level
+    file->fileLevel = PM_FPA_LEVEL_FPA;
+    file->dataLevel = PM_FPA_LEVEL_FPA;
+    file->freeLevel = PM_FPA_LEVEL_FPA;
+
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineChipMosaic(const pmConfig *config, pmFPA *src, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(src, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->cameraName, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->formatName, NULL);
+
+    pmFPAfile *file;                    // The new file
+    if (config->cameraName[0] == '_' &&
+        (strcmp(config->cameraName + strlen(config->cameraName) - 5, "-CHIP") == 0 ||
+         strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0)) {
+        // The input camera has already been mosaicked to this level
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, config->cameraName, config->formatName);
+    } else {
+        psString cameraName = NULL; // Name of the new (automatically-generated) camera configuration
+        if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 4 , "-FPA") == 0) {
+            cameraName = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 5);
+        } else {
+            cameraName = psMemIncrRefCounter(config->cameraName);
+        }
+        psString newCameraName = NULL;  // Name of the new (automatically-generated) camera configuration
+        psStringAppend(&newCameraName, "_%s-CHIP", cameraName);
+
+        // Find the correct camera configuration
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, newCameraName, config->formatName);
+        psFree(newCameraName);
+        psFree(cameraName);
+    }
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    file->src = psMemIncrRefCounter(src); // inherit output elements from this source pmFPA
+
+    // inherit the concepts from the src fpa:
+    pmFPACopyConcepts(file->fpa, file->src);
+
+    file->mosaicLevel = PM_FPA_LEVEL_CHIP; // don't do any I/O on this at a lower level
+
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineFPAMosaic(const pmConfig *config, pmFPA *src, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(src, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->cameraName, NULL);
+
+    pmFPAfile *file;                    // The new file
+    if (config->cameraName[0] == '_' &&
+        (strcmp(config->cameraName + strlen(config->cameraName) - 4 , "-FPA") == 0 ||
+         strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0)) {
+        // The input camera has already been mosaicked to this level
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, config->cameraName, config->formatName);
+    } else {
+
+        psString original = NULL;       // Name of the original camera configuration
+        if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 5 , "-CHIP") == 0) {
+            // It's a chip mosaic; we need to get the original name
+            original = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 6);
+        } else if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0) {
+            original = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 9);
+        } else {
+            original = psMemIncrRefCounter(config->cameraName);
+        }
+        psString cameraName = NULL;
+        psStringAppend(&cameraName, "_%s-FPA", original);
+        psFree(original);
+
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, cameraName, config->formatName);
+        psFree(cameraName);
+    }
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    file->src = psMemIncrRefCounter(src); // inherit output elements from this source pmFPA
+
+    file->mosaicLevel = PM_FPA_LEVEL_FPA; // don't do any I/O on this at a lower level
+
+    return file;
+}
+
+// create a file with the given name, assign it type "INTERNAL", and supply it with an image
+// of the requested dimensions. (image only, mask and weight are ignored)
+pmReadout *pmFPAfileDefineInternal (psMetadata *files, const char *name, int Nx, int Ny, int type)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    pmReadout *readout = pmReadoutAlloc(NULL);
+    readout->image = psImageAlloc(Nx, Ny, type);
+
+    // I want an image from the
+    pmFPAfile *file = pmFPAfileAlloc();
+    file->mode = PM_FPA_MODE_INTERNAL;
+    file->name = psStringCopy (name);
+
+    file->readout = readout;
+    psMetadataAddPtr(files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+    psFree(file);
+    // we free this copy of file, but 'files' still has a copy
+
+    return readout;
+}
+
+bool pmFPAfileDropInternal(psMetadata *files, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    bool status = false;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (!status) {
+        psTrace("psModules.camera", 6, "Internal File %s not in file list", name);
+        return true;
+    }
+    if (file == NULL) {
+        psError(PS_ERR_IO, true, "file %s is NULL", name);
+        return false;
+    }
+    if (file->mode != PM_FPA_MODE_INTERNAL) {
+        psTrace("psModules.camera", 6, "FPA File %s not Internal, not dropping", name);
+        return true;
+    }
+
+    psTrace("psModules.camera", 6, "dropping Internal FPA File %s", name);
+    psMetadataRemoveKey (files, name);
+    return true;
+}
+
+// Select or construct the requested readout.  If the named entry does not exist, generate it based
+// on the specified fpa and binning.  We have 4 possibilities: (INTERNAL or I/O file) and (exists or
+// not).  This call is used after all user-requested pmFPAfiles have been generated.  A missing
+// pmFPAfile is being used internally.
+pmReadout *pmFPAGenerateReadout(const pmConfig *config, // configuration information
+				const pmFPAview *view, // select background for this entry
+				const char *name, // name of internal/external file
+				const pmFPA *fpa, // use this fpa to generate
+				const psImageBinning *binning) {
+  pmReadout *readout = NULL;
+
+  bool status = true;
+  pmFPAfile *file = psMetadataLookupPtr(&status, config->files, name);
+
+  // if the file does not exist, it is not being used as an I/O file: define an internal version
+  if (file == NULL) {
+    readout = pmFPAfileDefineInternal (config->files, name, binning->nXruff, binning->nYruff, PS_TYPE_F32);
+    return readout;
+  } 
+
+  // if the mode is INTERNAL, it has been defined in a previous call.  XXX This seems to require
+  // that the readout have the same dimensions for all entries.
+  if (file->mode == PM_FPA_MODE_INTERNAL) {
+    readout = file->readout;
+    return readout;
+  } 
+
+  // we are using this pmFPAfile as an I/O file: select readout or create
+  readout = pmFPAviewThisReadout (view, file->fpa);
+  if (readout == NULL) {
+    // readout does not yet exist: create from input
+    // XXX we have an inconsistency in this calculation here and in pmFPACopy
+    // XXX use the psImageBinning functions to set the output image size
+    if (binning == NULL) {
+      pmFPAfileCopyStructureView (file->fpa, fpa, 1, 1, view);
+      readout = pmFPAviewThisReadout (view, file->fpa);
+    } else {
+      pmFPAfileCopyStructureView (file->fpa, fpa, binning->nXbin, binning->nYbin, view);
+      readout = pmFPAviewThisReadout (view, file->fpa);
+      PS_ASSERT (binning->nXruff == readout->image->numCols, false);
+      PS_ASSERT (binning->nYruff == readout->image->numRows, false);
+    }
+  }
+
+  return readout;
+}
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileDefine.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileDefine.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileDefine.h	(revision 22290)
@@ -0,0 +1,147 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.16 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-27 02:08:19 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+#ifndef PM_FPA_FILE_DEFINE_H
+#define PM_FPA_FILE_DEFINE_H
+
+// load the pmFPAfile information from the camera configuration data
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.  Multiple file rules of the same name are permitted
+// if multiple is true.
+pmFPAfile *pmFPAfileDefineInput (const pmConfig *config, pmFPA *fpa, const char *name);
+
+// load the pmFPAfile information from the camera configuration data
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+// Define an output pmFPAfile
+pmFPAfile *pmFPAfileDefineOutput(const pmConfig *config, // Configuration
+                                 pmFPA *fpa, // Optional FPA to bind
+                                 const char *name // Name of file rule
+    );
+
+/// Same as pmFPAfileDefineOutput, but binds to the fpa in the provided file
+pmFPAfile *pmFPAfileDefineOutputFromFile(const pmConfig *config, // Configuration
+                                         pmFPAfile *file, // File to bind FPAs, or NULL
+                                         const char *name // Name of file rule
+    );
+
+/// Define the FPA file using the provided camera and format names.
+pmFPAfile *pmFPAfileDefineOutputForFormat(const pmConfig *config, // Configuration
+                                          pmFPA *fpa, // Optional FPA to bind
+                                          const char *name, // Name of file rule
+                                          psString cameraName, // Name of camera configuration to use
+                                          psString formatName // Name of camera format to use
+    );
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromArgs (bool *found, pmConfig *config, const char *filename, const char *argname);
+
+// look for the given argname on the argument list; bind the associated files to the specified
+// fpa.  these are, eg, mask or weight images.
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileBindFromArgs (bool *found, pmFPAfile *input, pmConfig *config, const char *filename, const char *argname);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromConf (bool *found, const pmConfig *config, const char *filename);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromDetDB (bool *found, const pmConfig *config, const char *filename,
+                                     pmFPA *input, pmDetrendType type);
+
+// create a new output pmFPAfile based on an existing FPA
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromFPA (const pmConfig *config, pmFPA *src, int xBin, int yBin, const char *filename);
+
+/// Same as pmFPAfileDefineFromFPA, except it uses an FPA file instead of an FPA
+pmFPAfile *pmFPAfileDefineFromFile(const pmConfig *config, // Configuration
+                                   pmFPAfile *src, // Source file for this file
+                                   int xBin, int yBin, // Binning for this file
+                                   const char *filename // Name of file rule
+    );
+
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineNewCamera (const pmConfig *config, const char *filename);
+
+/// Create a new output pmFPAfile for a skycell of the default camera
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmFPAfile *pmFPAfileDefineSkycell(const pmConfig *config, ///< Configuration data
+                                  pmFPA *fpa, ///< FPA to which to bind
+                                  const char *filename ///< Output (root) filename
+    );
+
+
+/// Create a new output pmFPAfile based upon a chip mosaic of an existing FPA
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmFPAfile *pmFPAfileDefineChipMosaic(const pmConfig *config, ///< Configuration data
+                                     pmFPA *src, ///< Source FPA
+                                     const char *filename ///< Output (root) filename
+                                    );
+
+/// Create a new output pmFPAfile based upon an FPA mosaic of an existing FPA
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmFPAfile *pmFPAfileDefineFPAMosaic(const pmConfig *config, ///< Configuration data
+                                    pmFPA *src, ///< Source FPA
+                                    const char *filename ///< Output (root) filename
+                                   );
+
+// create a file with the given name, assign it type "INTERNAL", and supply it with an image
+// of the requested dimensions. (image only, mask and weight are ignored)
+pmReadout *pmFPAfileDefineInternal (psMetadata *files, const char *name, int Nx, int Ny, int type);
+
+// delete the INTERNAL file of the given name (if it exists)
+bool pmFPAfileDropInternal (psMetadata *files, const char *name);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineSingleFromArgs (bool *found, pmConfig *config, const char *filename,
+        const char *argname, int entry);
+
+// Select or construct the requested readout.  If the named entry does not exist, generate it based
+// on the specified fpa and binning.  We have 4 possibilities: (INTERNAL or I/O file) and (exists or
+// not).  This call is used after all user-requested pmFPAfiles have been generated.  A missing
+// pmFPAfile is being used internally.
+pmReadout *pmFPAGenerateReadout(const pmConfig *config, // configuration information
+				const pmFPAview *view, // select background for this entry
+				const char *name, // name of internal/external file
+				const pmFPA *fpa, // use this fpa to generate
+				const psImageBinning *binning);
+
+/// @}
+# endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileFitsIO.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileFitsIO.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileFitsIO.c	(revision 22290)
@@ -0,0 +1,509 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfileFitsIO.h"
+#include "pmFPACopy.h"
+#include "pmFPAConstruct.h"
+
+pmFPA *pmFPAfileSuitableFPA(const pmFPAfile *file, const pmFPAview *view, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+    if (!file->format) {                // Working with the same output format as input format
+        return psMemIncrRefCounter(file->fpa);
+    }
+
+    // May need to change format
+    pmFPALevel level = pmFPAviewLevel(view); // Level for the view
+    if (level == PM_FPA_LEVEL_NONE || level == PM_FPA_LEVEL_READOUT) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "This function shouldn't be called at the readout (or unknown) level.");
+        return NULL;
+    }
+
+    // Does the HDU of interest conform to the desired format?
+    pmHDU *hdu = pmFPAviewThisHDU(view, file->fpa); // The HDU of interest
+    if (hdu && hdu->format == file->format) {
+        // No work required
+        return psMemIncrRefCounter(file->fpa);
+    }
+
+    // Otherwise, we have to generate a copy with the correct format
+
+    pmFPAview *phuView = pmFPAviewAlloc(0); // View corresponding to the PHU
+    *phuView = *view;               // Copy contents
+    pmFPALevel phuLevel = pmFPAPHULevel(file->format); // Level for the PHU
+    switch (phuLevel) {
+      case PM_FPA_LEVEL_FPA:
+        phuView->chip = -1;
+        // Flow through
+      case PM_FPA_LEVEL_CHIP:
+        phuView->cell = -1;
+        // Flow through
+      case PM_FPA_LEVEL_CELL:
+        phuView->readout = -1;
+        break;
+      case PM_FPA_LEVEL_READOUT:
+      case PM_FPA_LEVEL_NONE:
+      default:
+        psAbort("Should never get here: bad phu level.\n");
+    }
+
+    pmFPA *nameSource = file->src; // Source of FPA.NAME
+    if (!nameSource) {
+        nameSource = file->fpa;
+    }
+    bool mdok;                  // Status of MD lookup
+    const char *fpaname = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.NAME"); // Name of FPA
+
+    pmFPA *copy = pmFPAConstruct(file->camera);  // FPA to return
+    if (!pmFPAAddSourceFromView(copy, fpaname, phuView, file->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to insert HDU into FPA for writing.\n");
+        psFree(copy);
+        psFree(phuView);
+        return NULL;
+    }
+    psFree(phuView);
+
+    switch (level) {
+      case PM_FPA_LEVEL_FPA:
+        if (!pmFPACopy(copy, file->fpa)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to copy FPA for format conversion.\n");
+            return NULL;
+        }
+        return copy;
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = pmFPAviewThisChip(view, copy); // Chip of interest
+          pmChip *srcChip = pmFPAviewThisChip(view, file->fpa); // Source chip
+          if (!pmChipCopy(chip, srcChip)) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to copy chip for format conversion.\n");
+              return false;
+          }
+          return copy;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          pmCell *cell = pmFPAviewThisCell(view, copy); // Cell of interest
+          pmCell *srcCell = pmFPAviewThisCell(view, file->fpa); // Source cell
+          if (!pmCellCopy(cell, srcCell)) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to copy cell for format conversion.\n");
+              return false;
+          }
+          return copy;
+      }
+      case PM_FPA_LEVEL_READOUT:
+      case PM_FPA_LEVEL_NONE:
+      default:
+        psAbort("Should never get here: bad phu level.\n");
+    }
+
+    return NULL;
+
+}
+
+// given an already-opened fits file, read the table corresponding to the specified view
+bool pmFPAviewReadFitsTable(const pmFPAview *view, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;             // FPA of interest
+    psFits *fits = file->fits;          // FITS file
+
+    if (view->chip == -1) {
+        return pmFPAReadTable(fpa, fits, name) > 0;
+    }
+
+    if (view->cell == -1) {
+        pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+        return pmChipReadTable(chip, fits, name) > 0;
+    }
+
+    pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+    return pmCellReadTable(cell, fits, name) > 0;
+}
+
+// given an already-opened fits file, write the table corresponding to the specified view
+bool pmFPAviewWriteFitsTable(const pmFPAview *view, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;             // FPA of interest
+    psFits *fits = file->fits;          // FITS file
+
+    if (view->chip == -1) {
+        return pmFPAWriteTable(fits, fpa, name) > 0;
+    }
+
+    if (view->cell == -1) {
+        pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+        return pmChipWriteTable(fits, chip, name) > 0;
+    }
+
+    pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+    return pmCellWriteTable(fits, cell, name) > 0;
+}
+
+
+// given an already-opened fits file, read the components corresponding to the specified view
+static bool fpaViewReadFitsImage(const pmFPAview *view, // FPA view, specifying the level of interest
+                                 pmFPAfile *file, // FPA file of interest
+                                 bool (*fpaReadFunc)(pmFPA*, psFits*, psDB*), // Function to read FPA
+                                 bool (*chipReadFunc)(pmChip*, psFits*, psDB*), // Function to read chip
+                                 bool (*cellReadFunc)(pmCell*, psFits*, psDB*) // Function to read cell
+                                )
+{
+    assert(view);
+    assert(file);
+
+    pmFPA *fpa = file->fpa;             // FPA of interest
+    psFits *fits = file->fits;          // FITS file from which to read
+
+    if (view->chip == -1) {
+        return fpaReadFunc(fpa, fits, NULL);
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip]; // Chip of interest
+
+    if (view->cell == -1) {
+        return chipReadFunc(chip, fits, NULL);
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell]; // Cell of interest
+
+    if (view->readout == -1) {
+        return cellReadFunc(cell, fits, NULL);
+    }
+    psError(PS_ERR_UNKNOWN, true, "Bad view: %d,%d", view->chip, view->cell);
+    return false;
+
+    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
+    #if 0
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouts->n == %d",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (view->nRows == 0) {
+        pmReadoutRead (readout, fits, NULL);
+    } else {
+        pmReadoutReadSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
+    }
+    return true;
+    #endif
+}
+
+
+bool pmFPAviewReadFitsImage(const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, pmFPARead, pmChipRead, pmCellRead);
+}
+
+bool pmFPAviewReadFitsMask(const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, pmFPAReadMask, pmChipReadMask, pmCellReadMask);
+}
+
+bool pmFPAviewReadFitsWeight(const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, pmFPAReadWeight, pmChipReadWeight, pmCellReadWeight);
+}
+
+bool pmFPAviewReadFitsHeaderSet(const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, pmFPAReadHeaderSet, pmChipReadHeaderSet, pmCellReadHeaderSet);
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view. when the file was opened, pmFPA/Chip/CellWrite was
+// called on it with blank=true to write the (possible) blank PHU
+// do NOT call the functions below with blank=true or they will write
+// out data in an inconsistent fashion
+// the calls below should recurse down the element to write out all components.
+static bool fpaViewWriteFitsImage(const pmFPAview *view, // FPA view, specifying the level of interest
+                                  pmFPAfile *file, // FPA file of interest
+                                  const pmConfig *config, // Configuration
+                                  bool (*fpaWriteFunc)(pmFPA*, psFits*, psDB*, bool, bool), // Func for FPA
+                                  bool (*chipWriteFunc)(pmChip*, psFits*, psDB*, bool, bool), // Func for chip
+                                  bool (*cellWriteFunc)(pmCell*, psFits*, psDB*, bool) // Func for cell
+                                 )
+{
+    assert(view);
+    assert(file);
+
+    psFits *fits = file->fits;          // FITS file
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config); // FPA to write
+
+    switch (pmFPAviewLevel(view)) {
+    case PM_FPA_LEVEL_FPA: {
+            bool success = fpaWriteFunc(fpa, fits, NULL, false, true);
+            psFree(fpa);
+            return success;
+        }
+    case PM_FPA_LEVEL_CHIP: {
+            pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+            bool success = chipWriteFunc(chip, fits, NULL, false, true);
+            psFree(fpa);
+            return success;
+        }
+    case PM_FPA_LEVEL_CELL: {
+            pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+            bool success = cellWriteFunc(cell, fits, NULL, false);
+            psFree(fpa);
+            return success;
+        }
+    case PM_FPA_LEVEL_READOUT:
+        #if 0 // XXX disable readout write for now
+
+        {
+            pmReadout *readout = pmFPAviewThisReadout(view, file->fpa); // Readout of interest
+            if (changeFormat)
+        {
+            // No copy function defined for readouts!
+            psError(PS_ERR_UNKNOWN, false, "Unable to copy readout for format conversion on write.\n");
+                return false;
+            }
+            if (view->nRows == 0)
+        {
+            return pmReadoutWrite(readout, fits, NULL, NULL);
+            } else
+            {
+                return pmReadoutWriteSegment(readout, fits, view->nRows, view->iRows, NULL, NULL);
+            }
+        }
+        #endif
+    case PM_FPA_LEVEL_NONE:
+    default:
+        psAbort("Should never reach here: invalid file level.");
+    }
+
+    return false;
+}
+
+bool pmFPAviewWriteFitsImage(const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewWriteFitsImage(view, file, config, pmFPAWrite, pmChipWrite, pmCellWrite);
+}
+
+bool pmFPAviewWriteFitsMask(const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewWriteFitsImage(view, file, config, pmFPAWriteMask, pmChipWriteMask, pmCellWriteMask);
+}
+
+bool pmFPAviewWriteFitsWeight(const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewWriteFitsImage(view, file, config, pmFPAWriteWeight, pmChipWriteWeight, pmCellWriteWeight);
+}
+
+// given an already-opened fits file, read the components corresponding
+// to the specified view
+bool pmFPAviewFreeData(const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        psTrace ("pmFPAfile", 5, "freeing fpa for %s\n", file->filename);
+        pmFPAFreeData (fpa);
+        // XXX drop me: file->fpa = NULL;
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        psTrace ("pmFPAfile", 5, "freeing chip %d for %s\n", view->chip, file->filename);
+        pmChipFreeData (chip);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        psTrace ("pmFPAfile", 5, "freeing cell %d for %s\n", view->cell, file->filename);
+        pmCellFreeData (cell);
+        return true;
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+
+    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
+    #if 0
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouts->n == %d",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (view->nRows == 0) {
+        pmReadoutRead (readout, fits, NULL);
+    } else {
+        pmReadoutReadSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
+    }
+    return true;
+    #endif
+}
+
+#if 0
+// Shouldn't need this --- when we want to free fringe data, we want to free the whole level, not just the
+// table.
+
+// Free the table within a cell
+static void freeTable(pmCell *cell,     // Cell of interest
+                      const char *name  // Name of table to free
+                     )
+{
+    assert(cell);
+    assert(name && strlen(name) > 0);
+
+    psString headerName = NULL;         // Name of header
+    psStringAppend(&headerName, "%s.HEADER", name);
+    if (psMetadataLookup(cell->analysis, headerName)) {
+        psMetadataRemoveKey(cell->analysis, headerName);
+    }
+    psFree(headerName);
+
+    if (psMetadataLookup(cell->analysis, name)) {
+        psMetadataRemoveKey(cell->analysis, name);
+    }
+
+    return;
+}
+
+// given a file, free the components corresponding to the specified view
+bool pmFPAviewFreeFitsTable (const pmFPAview *view, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        psArray *chips = fpa->chips;    // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i]; // Chip of interest
+            psArray *cells = chip->cells; // Array of cells
+            for (int j = 0; j < cells->n; j++) {
+                pmCell *cell = cells->data[j]; // Cell of interest
+                freeTable(cell, name);
+            }
+        }
+        return true;
+    }
+
+    if (view->cell == -1) {
+        pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+        psArray *cells = chip->cells;   // Array of cells
+        for (int i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i]; // Cell of interest
+            freeTable(cell, name);
+        }
+        return true;
+    }
+
+    pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+    freeTable(cell, name);
+    return true;
+}
+
+#endif
+
+bool pmFPAviewFitsWritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    bool status = false;
+
+    if (file->mode != PM_FPA_MODE_WRITE) return true;
+    if (file->wrote_phu) return true;
+
+    // select or generate the desired fpa in the correct output format
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config);
+    pmHDU *phu = pmFPAviewThisHDU(view, fpa);
+    if (!phu || !phu->blankPHU) {
+        // No PHU to write!
+        psFree(fpa);
+        return true;
+    }
+
+    switch (file->fileLevel) {
+      case PM_FPA_LEVEL_FPA:
+        status = pmFPAWrite(fpa, file->fits, NULL, true, false);
+        break;
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = pmFPAviewThisChip(view, fpa);
+          status = pmChipWrite(chip, file->fits, NULL, true, false);
+          break;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          pmCell *cell = pmFPAviewThisCell(view, fpa);
+          status = pmCellWrite(cell, file->fits, NULL, true);
+          break;
+      }
+      default:
+        psAbort("fileLevel not correctly set");
+        break;
+    }
+
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write PHU for Image %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+
+    psFree(fpa);
+    file->wrote_phu = true;
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileFitsIO.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileFitsIO.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileFitsIO.h	(revision 22290)
@@ -0,0 +1,88 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ * @author PAP, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-07-14 03:19:01 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FILE_FITS_IO_H
+#define PM_FPA_FILE_FITS_IO_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Read an image into the current view
+bool pmFPAviewReadFitsImage(const pmFPAview *view, ///< View specifying level of interest
+                            pmFPAfile *file ///< FPA file into which to read
+                           );
+
+/// Read a mask into the current view
+bool pmFPAviewReadFitsMask(const pmFPAview *view, ///< View specifying level of interest
+                           pmFPAfile *file ///< FPA file into which to read
+                          );
+/// Read a weight map into the current view
+bool pmFPAviewReadFitsWeight(const pmFPAview *view,  ///< View specifying level of interest
+                             pmFPAfile *file ///< FPA file into which to read
+                            );
+
+/// Read an image header into the current view
+bool pmFPAviewReadFitsHeaderSet(const pmFPAview *view,  ///< View specifying level of interest
+                                pmFPAfile *file ///< FPA file into which to read
+    );
+
+/// Write the image for the specified view
+bool pmFPAviewWriteFitsImage(const pmFPAview *view, ///< View specifying level of interest
+                             pmFPAfile *file, ///< FPA file to write
+                             const pmConfig *config ///< Configuration
+                            );
+
+/// Write the mask for the specified view
+bool pmFPAviewWriteFitsMask(const pmFPAview *view, ///< View specifying level of interest
+                            pmFPAfile *file, ///< FPA file to write
+                            const pmConfig *config ///< Configuration
+                           );
+
+/// Write the weight map for the specified view
+bool pmFPAviewWriteFitsWeight(const pmFPAview *view, ///< View specifying level of interest
+                              pmFPAfile *file, ///< FPA file to write
+                              const pmConfig *config ///< Configuration
+                             );
+
+/// Write a PHU for a fits image if needed
+bool pmFPAviewFitsWritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+/// Free the data for the specified view
+bool pmFPAviewFreeData(const pmFPAview *view, ///< View specifying level of interest
+                       pmFPAfile *file  ///< FPA file to free data
+                      );
+
+/// Read a table into the current view
+bool pmFPAviewReadFitsTable(const pmFPAview *view, ///<  View specifying level of interest
+                            pmFPAfile *file, ///< FPA file into which to read
+                            const char *name ///< Name of table
+                           );
+
+/// Write the table for the specified view
+bool pmFPAviewWriteFitsTable(const pmFPAview *view, ///<  View specifying level of interest
+                             pmFPAfile *file, ///< FPA file to write
+                             const char *name ///< Name of table
+                            );
+
+/// Produce a suitable FPA for writing, on the basis of the input FPAfile
+///
+/// A new FPA with a changed format is generated if required (file->format is set and file->camera is equal to
+/// the default, indicating a change in the format without changing the camera --- changes to the camera are
+/// handled using other systems --- see pmFPAfileDefineChipMosaic, pmFPAfileDefineFPAMosaic).  Otherwise the
+/// file->fpa is returned (incremented).
+pmFPA *pmFPAfileSuitableFPA(const pmFPAfile *file,///< File containing the fpa
+                            const pmFPAview *view, ///< View at which to produce the fpa
+                            const pmConfig *config ///< Configuration
+                           );
+
+/// @}
+
+# endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileIO.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileIO.c	(revision 22290)
@@ -0,0 +1,954 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmFPAfile.h"
+#include "pmFPACopy.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmFPAfileIO.h"
+#include "pmFPAfileFitsIO.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceIO.h"
+#include "pmResiduals.h"
+#include "pmPSF_IO.h"
+#include "pmAstrometryTable.h"
+#include "pmFPA_JPEG.h"
+#include "pmSourcePlots.h"
+#include "pmFPAConstruct.h"
+#include "pmConcepts.h"
+
+// attempt create, read, write, close, or free pmFPAfiles available in files files are
+// automatically opened before they are read.  In the case of MEF files, the PHU is
+// read when the file is opened and written before the first extension is written.
+bool pmFPAfileIOChecks (pmConfig *config, const pmFPAview *view, pmFPAfilePlace place)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(config->files, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    psMetadata *files = config->files;
+
+    // attempt to perform all create, read, write, close operations
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        switch (place) {
+        case PM_FPA_BEFORE:
+            if (!pmFPAfileRead (file, view, config)) {
+                psError(PS_ERR_IO, false, "failed READ in FPA_BEFORE block for %s", file->name);
+                goto failure;
+            }
+            if (!pmFPAfileCreate(file, view, config)) {
+                psError(PS_ERR_IO, false, "failed CREATE in FPA_BEFORE block for %s", file->name);
+                goto failure;
+            }
+            break;
+        case PM_FPA_AFTER:
+            if (!pmFPAfileWrite (file, view, config)) {
+                psError(PS_ERR_IO, false, "failed WRITE in FPA_AFTER block for %s", file->name);
+                goto failure;
+            }
+            if (!pmFPAfileClose(file, view)) {
+                psError(PS_ERR_IO, false, "failed CLOSE in FPA_AFTER block for %s", file->name);
+                goto failure;
+            }
+            break;
+        default:
+            psAbort("You can't get here");
+        }
+    }
+
+    // attempt to free data that is no longer needed
+    psMetadataIteratorSet (iter, PS_LIST_HEAD);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        switch (place) {
+        case PM_FPA_BEFORE:
+            break;
+        case PM_FPA_AFTER:
+            if (!pmFPAfileFreeData(file, view)) {
+                if (!psMetadataRemoveKey(files, file->name)) {
+                    psError(PS_ERR_IO, false, "failed to remove %s in FPA_AFTER block", file->name);
+                    goto failure;
+                }
+            }
+            break;
+        default:
+            psAbort("You can't get here");
+        }
+    }
+    psFree (iter);
+    return true;
+
+failure:
+    psFree (iter);
+    return false;
+}
+
+// read the file, if necessary and possible
+bool pmFPAfileRead(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // an internal file should not be sent here (should not be left on config->files)
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    // skip the following states
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip read for %s, file is inactive", file->name);
+        return true;
+    }
+    if (file->mode != PM_FPA_MODE_READ) {
+        psTrace("psModules.camera", 6, "skip read for %s, mode is not READ", file->name);
+        return true;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // do we need to read this file? defer until we read the correct level
+    if (level != file->dataLevel) {
+        psTrace("psModules.camera", 6, "skip reading of %s at this level %s: dataLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
+        return true;
+    }
+
+    // do we need to open this file?
+    if (level >= file->fileLevel) {
+        // we are allowed to open a file at a level which is not the fileLevel, but we need to
+        // supply a view at the fileLevel for the file lookup functions below
+        pmFPAview *fileView = pmFPAviewForLevel (file->fileLevel, view);
+        if (!pmFPAfileOpen (file, fileView, config)) {
+            psError(PS_ERR_IO, false, "failed to open file %s (%s)", file->filename, file->name);
+            psFree (fileView);
+            return false;
+        }
+        psFree (fileView);
+    }
+
+    // We need to read it --- double-check it's open!
+    if (file->state == PM_FPA_STATE_CLOSED) {
+        psError(PS_ERR_IO, false, "failed to open file %s when attempting to read", file->name);
+        return false;
+    }
+
+    // select a reading method
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+        status = pmFPAviewReadFitsImage(view, file);
+        break;
+      case PM_FPA_FILE_MASK:
+        status = pmFPAviewReadFitsMask(view, file);
+        break;
+      case PM_FPA_FILE_WEIGHT:
+        status = pmFPAviewReadFitsWeight(view, file);
+        break;
+      case PM_FPA_FILE_HEADER:
+        status = pmFPAviewReadFitsHeaderSet(view, file);
+        break;
+      case PM_FPA_FILE_FRINGE:
+        status = pmFPAviewReadFitsImage (view, file);
+        if (status) {
+            if (!pmFPAviewReadFitsTable(view, file, "FRINGE")) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read fringe data from %s.\n", file->filename);
+                return false;
+            }
+        }
+        break;
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_CMF:
+        status = pmFPAviewReadObjects (view, file, config);
+        break;
+      case PM_FPA_FILE_PSF:
+        status = pmPSFmodelReadForView (view, file, config);
+        break;
+      case PM_FPA_FILE_ASTROM:
+        status = pmAstromReadForView (view, file, config);
+        break;
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        break;
+      default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d (%s)", file->type, file->name);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to read %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    psTrace ("psModules.camera", 5, "read %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+    return true;
+}
+
+// create the data elements (headers, images) appropriate for this view
+bool pmFPAfileCreate (pmFPAfile *file, const pmFPAview *view, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // these are not error conditions; these are state tests
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip create for inactive file %s", file->name);
+        return true;
+    }
+    if (file->mode != PM_FPA_MODE_WRITE) {
+        psTrace("psModules.camera", 6, "skip create for non-write file %s", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // don't create the file if the src (FPA) is not defined
+    if (file->src == NULL) {
+        psTrace("psModules.camera", 6, "skip create for FPA without src FPA for %s", file->name);
+        return true;
+    }
+
+    // do we need to write this file?
+    if (level != file->fileLevel || file->mosaicLevel != PM_FPA_LEVEL_NONE) {
+        psTrace("psModules.camera", 6, "skip creation of %s at this level %s: fileLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->fileLevel));
+        return true;
+    }
+
+    switch (file->type) {
+    case PM_FPA_FILE_IMAGE:
+    case PM_FPA_FILE_MASK:
+    case PM_FPA_FILE_WEIGHT:
+    case PM_FPA_FILE_FRINGE: {
+            // create FPA structure component based on view
+            psMetadata *format = file->format; // Camera format configuration
+            if (!format) {
+                format = config->format;
+            }
+
+            pmFPA *nameSource = file->src; // Source of FPA.NAME
+            if (!nameSource) {
+                nameSource = file->fpa;
+            }
+            bool mdok;                  // Status of MD lookup
+            const char *fpaname = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.NAME"); // Name of FPA
+
+            pmFPAAddSourceFromView(file->fpa, fpaname, view, format);
+            psTrace ("psModules.camera", 5, "created fpa data elements for %s (%s) (%d:%d:%d)\n", file->name, file->name, view->chip, view->cell, view->readout);
+            break;
+    }
+    case PM_FPA_FILE_HEADER:
+      psAbort ("Create not defined for HEADER");
+      break;
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_CMF:
+    case PM_FPA_FILE_PSF:
+    case PM_FPA_FILE_ASTROM:
+    case PM_FPA_FILE_JPEG:
+    case PM_FPA_FILE_KAPA:
+        break;
+
+    default:
+        psError(PS_ERR_IO, true, "Unsupported type for %s: %d", file->name, file->type);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileWrite(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip write for %s, files is inactive", file->name);
+        return true;
+    }
+
+    if (file->mode != PM_FPA_MODE_WRITE) {
+        psTrace("psModules.camera", 6, "skip write for %s, mode is not WRITE", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+
+    if (!file->save) {
+        psTrace("psModules.camera", 6, "skip write for %s, save is FALSE", file->name);
+        return true;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // the effective dataLevel (for mosaics, we have preserved the original dataLevel)
+    pmFPALevel dataLevel = file->dataLevel;
+    if (file->mosaicLevel != PM_FPA_LEVEL_NONE && file->mosaicLevel < dataLevel) {
+        dataLevel = file->mosaicLevel;
+    }
+
+    // do we need to write this file?
+    if (level != dataLevel) {
+        psTrace("psModules.camera", 6, "skip writing of %s at this level %s: dataLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(dataLevel));
+        return true;
+    }
+
+    // do we have data to write at this level?
+    if (!pmFPAviewCheckDataStatus (file->fpa, view)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+    }
+
+    // note that for CMF and PSF, the test above is not sufficient to determine if there
+    // is actually any data to write out.  this is because these types of output files
+    // have their data stored on the readout->analysis metadata structure of another
+    // (existing) fpa
+    if (file->type == PM_FPA_FILE_CMF) {
+      if (!pmFPAviewCheckDataStatusForSources (view, file)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+      }
+    }
+    if (file->type == PM_FPA_FILE_PSF) {
+      if (!pmPSFmodelCheckDataStatusForView (view, file)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+      }
+    }
+    if (file->type == PM_FPA_FILE_ASTROM) {
+      if (!pmAstromCheckDataStatusForView (view, file)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+      }
+    }
+
+    // open the file if not yet opened
+    // XXX do we need to test mosaicLevel?
+    if (level >= file->fileLevel) {
+        // we are allowed to open a file at a level which is not the fileLevel, but
+        // we need to supply view at the fileLevel for the file lookup functions below
+        pmFPAview *fileView = pmFPAviewForLevel (file->fileLevel, view);
+        if (!pmFPAfileOpen (file, fileView, config)) {
+            psError(PS_ERR_IO, false, "failed to open %s (%s)", file->filename, file->name);
+            psFree (fileView);
+            return false;
+        }
+
+        // do we need to write out a PHU?
+        if (!pmFPAfileWritePHU(file, fileView, config)) {
+            psError(PS_ERR_IO, false, "failed to write phu for %s (%s)", file->filename, file->name);
+            return false;
+        }
+
+        psFree (fileView);
+    }
+
+    if (file->compression) {
+        psTrace("psModules.camera", 7, "Setting compression for %s (%s)\n", file->filename, file->name);
+        if (!psFitsCompressionApply(file->fits, file->compression)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to set compression options for %s (%s) (%d:%d:%d)\n",
+                    file->filename, file->name, view->chip, view->cell, view->readout);
+            return false;
+        }
+    }
+
+
+    // Ensure headers and all are updated
+    // This is here so that the individual write functions (e.g., images, PSFs, sources, etc) don't have to
+    // take care of all this themselves (because they generally don't).
+    {
+        pmHDU *hdu = pmFPAviewThisHDU(view, file->fpa);
+        if (hdu) {
+            if (!hdu->header) {
+                hdu->header = psMetadataAlloc();
+            }
+            pmConfigConformHeader(hdu->header, file->format);
+        }
+
+        pmFPA *fpa = file->fpa;         // FPA of interest
+        pmChip *chip = pmFPAviewThisChip(view, file->fpa); // Chip of interest, or NULL
+        pmCell *cell = pmFPAviewThisCell(view, file->fpa); // Cell of interest, or NULL
+        pmFPAUpdateNames(fpa, chip, cell);
+
+        pmConceptSource sources = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+            PM_CONCEPT_SOURCE_DEFAULTS; // Concept sources to write
+        if (cell) {
+            if (!pmConceptsWriteCell(cell, sources, true, NULL)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for cell.\n");
+                return false;
+            }
+        } else if (chip) {
+            if (!pmConceptsWriteChip(chip, sources, true, true, NULL)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for chip.\n");
+                return false;
+            }
+        } else if (!pmConceptsWriteFPA(fpa, sources, true, NULL)) {
+            psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
+            return false;
+        }
+    }
+
+    // select a writing method
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+        status = pmFPAviewWriteFitsImage(view, file, config);
+        break;
+      case PM_FPA_FILE_MASK:
+        status = pmFPAviewWriteFitsMask(view, file, config);
+        break;
+      case PM_FPA_FILE_WEIGHT:
+        status = pmFPAviewWriteFitsWeight(view, file, config);
+        break;
+      case PM_FPA_FILE_HEADER:
+        psAbort ("no HEADER write functions defined");
+        break;
+      case PM_FPA_FILE_FRINGE:
+        status = pmFPAviewWriteFitsImage (view, file, config);
+        if (status) {
+            if (!pmFPAviewWriteFitsTable(view, file, "FRINGE")) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to write fringe data from %s.\n", file->filename);
+                return false;
+            }
+        }
+        break;
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_CMF:
+        status = pmFPAviewWriteObjects (view, file, config);
+        break;
+
+      case PM_FPA_FILE_PSF:
+        status = pmPSFmodelWriteForView (view, file, config);
+        break;
+
+      case PM_FPA_FILE_ASTROM:
+        status = pmAstromWriteForView (view, file, config);
+        break;
+
+      case PM_FPA_FILE_JPEG:
+        status = pmFPAviewWriteJPEG (view, file, config);
+        break;
+
+      case PM_FPA_FILE_KAPA:
+        status = pmFPAviewWriteSourcePlot (view, file, config);
+        break;
+
+      default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d (%s)", file->type, file->name);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    psTrace ("psModules.camera", 5, "wrote %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+    return true;
+}
+
+bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // an internal file should not be sent here (should not be left on config->files)
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    // skip the following states
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip close for %s, files is inactive", file->name);
+        return true;
+    }
+    if (file->state == PM_FPA_STATE_CLOSED) {
+        psTrace("psModules.camera", 6, "skip close for %s, files is closed", file->name);
+        return true;
+    }
+
+    // is current level == open level?
+    pmFPALevel level = pmFPAviewLevel (view);
+    if (file->fileLevel != level) {
+        psTrace("psModules.camera", 6, "skip closing of %s at this level %s: fileLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->fileLevel));
+        return true;
+    }
+
+    // check if we are actually open
+    bool status = true;
+    switch (file->type) {
+        // check the FITS types
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_HEADER:
+      case PM_FPA_FILE_FRINGE:
+      case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_PSF:
+      case PM_FPA_FILE_ASTROM:
+        psTrace ("psModules.camera", 5, "closing %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+        status = psFitsClose (file->fits);
+        file->fits = NULL;
+        file->header = NULL;
+        file->state = PM_FPA_STATE_CLOSED;
+        file->wrote_phu = false;
+        break;
+
+        // ignore the TEXT types
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        break;
+
+      default:
+        psError(PS_ERR_IO, true, "type mismatch: %d (%s)", file->type, file->name);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to close %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileFreeData(pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // an internal file should not be sent here (should not be left on config->files)
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip free for %s, files is inactive", file->name);
+        return true;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // do we need to free this file?
+    if (level != file->freeLevel) {
+        psTrace("psModules.camera", 6, "skip free of %s at this level %s: freeLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->freeLevel));
+        return true;
+    }
+
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_HEADER:
+      case PM_FPA_FILE_FRINGE:
+        status = pmFPAviewFreeData(view, file);
+        break;
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_PSF:
+      case PM_FPA_FILE_ASTROM:
+        psTrace ("psModules.camera", 5, "NOT freeing %s (%s) : save for further analysis\n", file->filename, file->name);
+        return true;
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        psTrace ("psModules.camera", 5, "nothing to free for %s (%s)\n", file->filename, file->name);
+        return true;
+      default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d", file->type);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to read %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    psTrace ("psModules.camera", 5, "freed %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+    return true;
+}
+
+// open file (if not already opened).
+// this function is only called only within pmFPAfileRead or pmFPAfileWrite.
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    bool status;
+    char *mode = NULL;
+    char *readMode = "r";
+    char *writeMode = "w";
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip open for %s, files is inactive", file->name);
+        return true;
+    }
+
+    if (file->state == PM_FPA_STATE_OPEN) {
+        return true;
+    }
+
+    // these are programming errors
+    PS_ASSERT(file->mode != PM_FPA_MODE_NONE, false);
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    if (file->mode == PM_FPA_MODE_READ) {
+        mode = readMode;
+    }
+    if (file->mode == PM_FPA_MODE_WRITE) {
+        mode = writeMode;
+    }
+    if ((file->mode == PM_FPA_MODE_WRITE) && !file->save) {
+        psTrace("psModules.camera", 6, "skip open for %s, output file not requested", file->name);
+        return true;
+    }
+
+    // determine the file name, free a name allocated earlier
+    psFree (file->filename);
+    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+    if (file->filename == NULL) {
+        psError(PS_ERR_IO, true, "Filename is NULL");
+        return false;
+    }
+
+    // indirect filenames: these come from a list on the command line or elsewhere
+    if (!strcasecmp (file->filename, "@FILES")) {
+        char *filesrc = pmFPAfileNameFromRule (file->filesrc, file, view);
+        if (filesrc == NULL) {
+            psError(PS_ERR_IO, false, "error converting filesrc to name %s\n", file->filesrc);
+            return false;
+        }
+
+        psFree (file->filename);
+        file->filename = psMetadataLookupStr (&status, file->names, filesrc);
+
+        if (file->filename == NULL) {
+            psError(PS_ERR_IO, true, "filename lookup error (@FILES) for %s : %s\n", file->filesrc, filesrc);
+            psFree (filesrc);
+            return false;
+        }
+        // psMetadataLookupStr just returns a view, file->filename must be protected
+        psMemIncrRefCounter (file->filename);
+        psFree (filesrc);
+    }
+
+    // get name from detrend database
+    // file->detrend->detID contains the desired -det_id detID -iteration iter string
+    if (!strcasecmp (file->filename, "@DETDB")) {
+        if (!file->detrend) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find information about selected detrend.");
+            return false;
+        }
+
+        psString classId = NULL;        // The class identifier, to pass to pmDetrendFile
+        psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "CLASSID"); // Menu of class IDs
+        if (!status || !menu) {
+	    psError(PS_ERR_IO, false, "Unable to find CLASSID metadata in camera configuration");
+	    return false;
+        } 
+	const char *rule = psMetadataLookupStr(&status, menu, file->detrend->level); // Rule for class_id
+	if (!status || !rule || strlen(rule) == 0) {
+	    psError(PS_ERR_IO, false, "Unable to find %s in CLASSID in camera configuration", file->detrend->level);
+	    return false;
+	} 
+	classId = pmFPAfileNameFromRule(rule, file, view);
+	if (!classId) {
+	    psError(PS_ERR_IO, false, "error converting CLASSID rule to name: %s\n", rule);
+	    return false;
+	}
+        psTrace ("psModules.camera", 6, "looking for detrend (%s, %s)\n", file->detrend->detID, classId);
+        psFree (file->filename);
+
+        file->filename = pmDetrendFile(file->detrend->detID, classId, config);
+        if (file->filename == NULL) {
+            psError(PS_ERR_IO, false, "failed to find a valid detrend image for detID %s : classID %s\n", file->detrend->detID, classId);
+            psFree (classId);
+            return false;
+        }
+
+        psTrace ("psModules.camera", 6, "got detrend file %s\n", file->filename);
+        psFree (classId);
+    }
+
+    // apply filename mangling rules (file://, path://, neb://)
+    bool create = file->mode == PM_FPA_MODE_WRITE ? true : false;
+    psString tmpName = pmConfigConvertFilename (file->filename, config, create);
+    psFree (file->filename);
+    file->filename = tmpName;
+
+    switch (file->type) {
+        // open the FITS types:
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_HEADER:
+      case PM_FPA_FILE_FRINGE:
+      case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_PSF:
+      case PM_FPA_FILE_ASTROM:
+        psTrace ("psModules.camera", 5, "opening %s (%s) (%d:%d:%d)\n",
+                 file->filename, file->name, view->chip, view->cell, view->readout);
+        file->fits = psFitsOpen (file->filename, mode);
+        if (file->fits == NULL) {
+            psError(PS_ERR_IO, false, "error opening file %s\n", file->filename);
+            return false;
+        }
+        file->state = PM_FPA_STATE_OPEN;
+
+        // if needed, set the optional EXTWORD field based on the camera value
+        if (file->format) {
+            psMetadata *fileMenu = psMetadataLookupMetadata (NULL, file->format, "FILE");
+            if (!fileMenu) {
+                psError (PS_ERR_IO, true, "FILE METADATA missing from camera format %s\n",
+                         config->formatName);
+                return false;
+            }
+            char *extword = psMetadataLookupStr (&status, fileMenu, "EXTWORD");
+            if (status) {
+                psFitsSetExtnameWord (file->fits, extword);
+            }
+        }
+
+	// XXX these are probably only needed for WRITE files
+        if (file->compression) {
+            psTrace("psModules.camera", 7, "Setting compression for %s (%s)\n", file->filename, file->name);
+            if (!psFitsCompressionApply(file->fits, file->compression)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to set compression options for %s (%s) (%d:%d:%d)\n",
+                        file->filename, file->name, view->chip, view->cell, view->readout);
+                return false;
+            }
+        }
+
+        file->fits->floatType = file->floatType;
+	file->fits->bitpix = file->bitpix;
+
+        // In some cases, we need to read the PHU after we've opened the file.  This happens for the images
+        // supplied by the detrend database, which are only identified here (pmConfigConvertFilename).
+        if (!pmFPAfileReadPHU (file, view, config)) {
+            psError (PS_ERR_IO, true, "error reading PHU for %s (%s) (%d:%d:%d)\n",
+                     file->filename, file->name, view->chip, view->cell, view->readout);
+            return false;
+        }
+        break;
+
+        // defer opening TEXT types:
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        psTrace ("psModules.camera", 5, "defer opening %s\n", file->filename);
+        break;
+
+      default:
+        psError(PS_ERR_IO, true, "type mismatch for %s : %d\n", file->filename, file->type);
+        return false;
+    }
+    return true;
+}
+
+// for this file and view, if we need to read a PHU, read it.  return true for any non-error
+// condition. this function should be called by pmFPAfileOpen.
+bool pmFPAfileReadPHU (pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    // required conditions
+    if (file->mode != PM_FPA_MODE_READ) return true;
+    if (file->state != PM_FPA_STATE_OPEN) psAbort ("pmFPAfileReadPHU called on unopened file");
+    if (file->fpa == NULL) psAbort ("pmFPAfileReadPHU called on file without an FPA");
+
+    // check if we need to read a PHU (if not, return true)
+    switch (file->fileLevel) {
+      case PM_FPA_LEVEL_FPA:
+        if (file->fpa->hdu) return true;
+        break;
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = pmFPAviewThisChip(view, file->fpa);
+          if (!chip) psAbort ("inconsistent file/fpa: fileLevel is CHIP, view is FPA");
+          if (chip->hdu) return true;
+          break;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          pmCell *cell = pmFPAviewThisCell(view, file->fpa);
+          if (!cell) psAbort ("inconsistent file/fpa: fileLevel is CELL, view is FPA");
+          if (cell->hdu) return true;
+          break;
+      }
+      case PM_FPA_LEVEL_NONE:
+        // Might get here immediately after opening a file selected from the detrend database.
+        break;
+      default:
+        psAbort("fileLevel not correctly set");
+        break;
+    }
+
+    // XXX do we need to advance to the first HDU?
+    psMetadata *phu = psFitsReadHeader (NULL, file->fits);
+    if (!file->format) {
+        // XXX do we need to read the recipe here?  these files are supplemental, and probably
+        // do not need to re-load the recipes
+        file->format = pmConfigCameraFormatFromHeader (config, phu, false);
+        if (!file->format) {
+            psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", file->filename);
+            psFree(phu);
+            return false;
+        }
+        file->formatName = psStringCopy(config->formatName);
+
+    } else {
+        bool valid;
+        if (!pmConfigValidateCameraFormat (&valid, file->format, phu)) {
+            psError (PS_ERR_UNKNOWN, false, "Error in camera configuration\n");
+            psFree (phu);
+            return false;
+        }
+        if (!valid) {
+            psError(PS_ERR_IO, false, "file %s is not from the required camera", file->filename);
+            psFree (phu);
+            return false;
+        }
+    }
+    pmFPAview *thisView = pmFPAAddSourceFromHeader (file->fpa, phu, file->format);
+    assert (thisView); // XXX we are having some trouble with input psf files not having the Cell and fpa names matching.
+    psFree (thisView);
+    psFree (phu);
+    // XXX we can check the output view to be sure it corresponds to our current view
+    return true;
+}
+
+// XXX this function is only called from pmFPAfileWrite
+// XXX for each data type, there should be a function which writes the PHU, if needed
+bool pmFPAfileWritePHU(pmFPAfile *file, const pmFPAview *view, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->wrote_phu) return true;
+
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_FRINGE:
+        status = pmFPAviewFitsWritePHU (view, file, config);
+        break;
+      case PM_FPA_FILE_CMF:
+        status = pmSource_CMF_WritePHU (view, file, config);
+        break;
+      case PM_FPA_FILE_PSF:
+        status = pmPSFmodelWritePHU (view, file, config);
+        break;
+      case PM_FPA_FILE_ASTROM:
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        break;
+      default:
+        fprintf (stderr, "warning: type mismatch\n");
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write PHU for %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    // XXX this is also being set in the individual functions.  choose one or the other
+    file->wrote_phu = true;
+    return true;
+}
+
+// set the state of the specified pmFPAfile to active (state == true) or inactive
+// if name is NULL, set the state for all pmFPAfiles
+bool pmFPAfileActivate (psMetadata *files, bool state, char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+
+    // Do this as an iteration rather than a lookup, since we may want to turn on/off multiple files at once
+    // (if there are multiple files defined with the same name).
+
+    if (!name) {
+        psMetadataItem *item = NULL;
+        psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
+        while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+            pmFPAfile *file = item->data.V;
+            if (state) {
+                file->state &= PS_NOT_U8(PM_FPA_STATE_INACTIVE);
+            } else {
+                file->state |= PM_FPA_STATE_INACTIVE;
+            }
+        }
+        psFree (iter);
+        return true;
+    }
+
+    if (!psMetadataLookup(files, name)) {
+        // We activated every example of that file that we could find.
+        return true;
+    }
+
+    psString regex = NULL;              // Regular expression for psMetadataIteratorAlloc
+    if (name) {
+        psStringAppend(&regex, "^%s$", name);
+    }
+
+    int num = 0;                        // Number of files activated
+    psMetadataItem *item = NULL;        // Item from iteration
+    psMetadataIterator *iter = psMetadataIteratorAlloc(files, PS_LIST_HEAD, regex); // Iterator
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        pmFPAfile *file = item->data.V; // File of interest
+        if (state) {
+            file->state &= PS_NOT_U8(PM_FPA_STATE_INACTIVE);
+        } else {
+            file->state |= PM_FPA_STATE_INACTIVE;
+        }
+        num++;
+    }
+    psFree(iter);
+    psFree(regex);
+
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileIO.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileIO.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAfileIO.h	(revision 22290)
@@ -0,0 +1,45 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-04-14 03:22:47 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FILE_IO_H
+#define PM_FPA_FILE_IO_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// open the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+// read from the real file corresponding to the given pmFPAfile for the current view
+bool pmFPAfileRead (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+bool pmFPAfileCreate (pmFPAfile *file, const pmFPAview *view, const pmConfig *config);
+
+// write to the real file corresponding to the given pmFPAfile for the current view
+bool pmFPAfileWrite (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+// close the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view);
+
+// free the data at this level
+bool pmFPAfileFreeData(pmFPAfile *file, const pmFPAview *view);
+
+// set the state of the specified pmFPAfile to active (state == true) or inactive
+// if name is NULL, set the state for all pmFPAfiles
+bool pmFPAfileActivate (psMetadata *files, bool state, char *name);
+
+// examine all pmFPAfiles listed in the files and perform the needed I/O operations (open,read,write,close)
+bool pmFPAfileIOChecks (pmConfig *config, const pmFPAview *view, pmFPAfilePlace place);
+
+bool pmFPAfileWritePHU(pmFPAfile *file, const pmFPAview *view, const pmConfig *config);
+bool pmFPAfileReadPHU (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+/// @}
+# endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAview.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAview.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAview.c	(revision 22290)
@@ -0,0 +1,375 @@
+/** @file  pmFPAview.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.14 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:33:14 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmHDUUtils.h"
+#include "pmFPAview.h"
+
+static void pmFPAviewFree(pmFPAview *view)
+{
+    // No reason to keep this function, apart from the fact that it allows us to type the memBlock
+    return;
+}
+
+pmFPAview *pmFPAviewAlloc(int nRows)
+{
+    pmFPAview *view = psAlloc(sizeof(pmFPAview));
+    psMemSetDeallocator(view, (psFreeFunc) pmFPAviewFree);
+
+    view->nRows = nRows;
+    pmFPAviewReset(view);
+    return view;
+}
+
+bool psMemCheckFPAview(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmFPAviewFree);
+}
+
+
+bool pmFPAviewReset(pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    view->chip    = -1;
+    view->cell    = -1;
+    view->readout = -1;
+    view->iRows   =  0;
+    return true;
+}
+
+// return a view restricted to the level (must be >= the input level)
+pmFPAview *pmFPAviewForLevel(pmFPALevel level, const pmFPAview *input)
+{
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+
+    pmFPAview *output = pmFPAviewAlloc (input->nRows);
+    *output = *input;
+
+    switch (level) {
+      case PM_FPA_LEVEL_FPA:
+        output->chip = -1;
+      case PM_FPA_LEVEL_CHIP:
+        output->cell = -1;
+      case PM_FPA_LEVEL_CELL:
+        output->readout = -1;
+        break;
+      default:
+        break;
+    }
+    return output;
+}
+
+pmFPALevel pmFPAviewLevel(const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(view, PM_FPA_LEVEL_NONE);
+
+    if (view->chip < 0) {
+        return PM_FPA_LEVEL_FPA;
+    }
+    if (view->cell < 0) {
+        return PM_FPA_LEVEL_CHIP;
+    }
+    if (view->readout < 0) {
+        return PM_FPA_LEVEL_CELL;
+    }
+    return PM_FPA_LEVEL_READOUT;
+}
+
+pmChip *pmFPAviewThisChip(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, NULL);
+
+    if (view->chip < 0) {
+        return NULL;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return NULL;
+    }
+
+    pmChip *chip = fpa->chips->data[view->chip];
+    return chip;
+}
+
+pmChip *pmFPAviewNextChip(pmFPAview *view, const pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    view->cell = -1;
+    view->readout = -1;
+    view->iRows = 0;
+
+    // if there are no available chips, return NULL
+    if (fpa->chips->n <= 0) {
+        view->chip = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->chip < -1) {
+        view->chip = -1;
+    }
+
+    // increment to the next chip
+    view->chip += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->chip >= fpa->chips->n) {
+        view->chip = -1;
+        return NULL;
+    }
+
+    // get the correct chip pointer
+    pmChip *chip = fpa->chips->data[view->chip];
+    return (chip);
+}
+
+pmCell *pmFPAviewThisCell(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    if (view->cell < 0) {
+        return NULL;
+    }
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, NULL);
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    if (view->cell >= chip->cells->n) {
+        return NULL;
+    }
+
+    pmCell *cell = chip->cells->data[view->cell];
+    return cell;
+}
+
+pmCell *pmFPAviewNextCell (pmFPAview *view, const pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    view->readout = -1;
+    view->iRows = 0;
+
+    // if there are no available cells, return NULL
+    if (chip->cells->n <= 0) {
+        view->cell = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->cell < -1) {
+        view->cell = -1;
+    }
+
+    // increment to the next cell
+    view->cell += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->cell >= chip->cells->n) {
+        view->cell = -1;
+        return NULL;
+    }
+
+    // get the correct cell pointer
+    pmCell *cell = chip->cells->data[view->cell];
+    return (cell);
+}
+
+pmReadout *pmFPAviewThisReadout (const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    if (view->readout < 0) {
+        return NULL;
+    }
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, NULL);
+
+    if (view->readout >= cell->readouts->n) {
+        return NULL;
+    }
+
+    pmReadout *readout = cell->readouts->data[view->readout];
+    return readout;
+}
+
+pmReadout *pmFPAviewNextReadout (pmFPAview *view, const pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    view->iRows = 0;
+
+    // if there are no available cells, return NULL
+    if (cell->readouts->n <= 0) {
+        view->readout = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->readout < -1) {
+        view->readout = -1;
+    }
+
+    // increment to the next cell
+    view->readout += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->readout >= cell->readouts->n) {
+        view->readout = -1;
+        return NULL;
+    }
+
+    // get the correct cell pointer
+    pmReadout *readout = cell->readouts->data[view->readout];
+    return (readout);
+}
+
+pmHDU *pmFPAviewThisHDU(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    // the HDU is attached to a cell, chip or fpa
+    // if this view has a -1 for the level which contains the hdu,
+    // there is no unambiguous HDU
+
+    if (view->chip < 0) {
+        return pmHDUFromFPA (fpa);
+    }
+    if (view->cell < 0) {
+        return pmHDUFromChip (pmFPAviewThisChip (view, fpa));
+    }
+    if (view->readout < 0) {
+        return pmHDUFromCell (pmFPAviewThisCell (view, fpa));
+    }
+    return pmHDUFromReadout (pmFPAviewThisReadout (view, fpa));
+}
+
+pmHDU *pmFPAviewThisPHU(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    // select the HDU which corresponds to the PHU containing this view
+
+    pmHDU *hdu;
+    pmFPAview new;
+    pmChip *chip;
+    pmCell *cell;
+
+    new = *view;
+
+    if (view->chip < 0) {
+        hdu = pmHDUFromFPA (fpa);
+        if (!hdu)
+            return NULL;
+        if (hdu->blankPHU)
+            return hdu;
+        return NULL;
+    }
+    if (view->cell < 0) {
+        chip = pmFPAviewThisChip (view, fpa);
+        hdu  = pmHDUFromChip (chip);
+        if (!hdu)
+            return NULL;
+        if (hdu->blankPHU)
+            return hdu;
+        new.chip = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    if (view->readout < 0) {
+        cell = pmFPAviewThisCell (view, fpa);
+        hdu  = pmHDUFromCell (cell);
+        if (!hdu) {
+            psAbort("a split readout is not covered by the current paradigm");
+        }
+        if (hdu->blankPHU)
+            return hdu;
+        new.cell = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    return NULL;
+}
+
+pmFPAview *pmFPAviewGenerate(const pmFPA *fpa, const pmChip *chip, const pmCell *cell,
+                             const pmReadout *readout)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmFPAview *view = pmFPAviewAlloc(0);// View to return
+
+    if (!chip) {
+        return view;
+    }
+
+    for (view->chip = 0; view->chip < fpa->chips->n && fpa->chips->data[view->chip] != chip; view->chip++);
+    if (view->chip == fpa->chips->n) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find chip %p in fpa.", chip);
+        psFree(view);
+        return NULL;
+    }
+
+    if (!cell) {
+        return view;
+    }
+
+    for (view->cell = 0; view->cell < chip->cells->n && chip->cells->data[view->cell] != cell; view->cell++);
+    if (view->cell == chip->cells->n) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find cell %p in chip.", cell);
+        psFree(view);
+        return NULL;
+    }
+
+    if (!readout) {
+        return view;
+    }
+
+    for (view->readout = 0;
+         view->readout < cell->readouts->n && cell->readouts->data[view->readout] != readout;
+         view->readout++);
+    if (view->readout == cell->readouts->n) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find readout %p in cell.", readout);
+        psFree(view);
+        return NULL;
+    }
+
+    return view;
+}
+
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAview.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAview.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmFPAview.h	(revision 22290)
@@ -0,0 +1,127 @@
+/* @file pmFPA.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-01-02 20:33:14 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_VIEW_H
+#define PM_FPA_VIEW_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// #include "pmFPA.h"
+// #include "pmFPALevel.h"
+
+/// Identifier for FPA components
+///
+/// This structure allows the identification of a single component of the focal plane hierarchy (or multiple,
+/// if we consider selecting all those components below the selected component).  Components are identified on
+/// the basis of their chip, cell, readout index.  An index of -1 means all components at that level.
+/// Additionally, since readouts may be read piecemeal, there are additional indices for these.
+typedef struct
+{
+    int chip;                           ///< Number of the chip, or -1 for all
+    int cell;                           ///< Number of the cell, or -1 for all
+    int readout;                        ///< Number of the readout, or -1 for all
+    int nRows;                          ///< Maximum number of rows per readout segment read, or 0 for all
+    int iRows;                          ///< Starting point for this read
+}
+pmFPAview;
+
+/// Allocator for pmFPAview
+pmFPAview *pmFPAviewAlloc(int nRows);   ///< Maximum number of rows per readout segment read, or 0 for all
+bool psMemCheckFPAview(psPtr ptr);
+
+/// Reset a view to select all components
+bool pmFPAviewReset(pmFPAview *view     ///< View to reset
+                   );
+
+// return a view restricted to the level (must be >= the input level)
+pmFPAview *pmFPAviewForLevel(pmFPALevel level, const pmFPAview *input);
+
+/// Determine the current view level
+///
+/// Returns the level appropriate for the view
+pmFPALevel pmFPAviewLevel(const pmFPAview *view ///< View to examine
+                         );
+
+// Lookups
+
+/// Return the currently selected chip for this view
+///
+/// Returns NULL if the selection is not specific or invalid
+pmChip *pmFPAviewThisChip(const pmFPAview *view, ///< Current view
+                          const pmFPA *fpa ///< FPA containing chip
+                         );
+
+/// Return the currently selected cell for this view
+///
+/// Returns NULL if the selection is not specific or invalid
+pmCell *pmFPAviewThisCell(const pmFPAview *view, ///< Current view
+                          const pmFPA *fpa ///< FPA containing cell
+                         );
+
+/// Return the currently selected readout for this view
+///
+/// Returns NULL if the selection is not specific or invalid
+pmReadout *pmFPAviewThisReadout(const pmFPAview *view, ///< Current view
+                                const pmFPA *fpa ///< FPA containing readout
+                               );
+
+// Incrementors
+
+/// Advance view to the next chip
+///
+/// Returns NULL if there is no next
+pmChip *pmFPAviewNextChip(pmFPAview *view, ///< Current view
+                          const pmFPA *fpa, ///< FPA containing chips
+                          int nStep     ///< Number of chips to increment
+                         );
+
+/// Advance view to the next cell
+///
+/// Returns NULL if there is no next
+pmCell *pmFPAviewNextCell(pmFPAview *view, ///< Current view
+                          const pmFPA *fpa, ///< FPA containing cells
+                          int nStep     ///< Number of cells to increment
+                         );
+
+/// Advance view to the next readout
+///
+/// Returns NULL if there is no next
+pmReadout *pmFPAviewNextReadout(pmFPAview *view, ///< Current view
+                                const pmFPA *fpa, ///< FPA containing readouts
+                                int nStep ///< Number of readouts to increment
+                               );
+
+/// Return the HDU corresponding to the current view
+///
+/// Uses the pmHDUFrom* functions, combined with the view.
+pmHDU *pmFPAviewThisHDU(const pmFPAview *view, ///< Current view
+                        const pmFPA *fpa ///< FPA for view
+                       );
+
+/// Return the blank Primary HDU corresponding to the current view, if any
+///
+/// Similar to pmFPAviewThisHDU, except returns NULL if no HDU is found, or the HDU is not a blank Primary HDU
+pmHDU *pmFPAviewThisPHU(const pmFPAview *view, ///< Current view
+                        const pmFPA *fpa ///< FPA for view
+                       );
+
+/// Generate a view, given a chip, cell, readout.
+///
+/// Uses the pointer value in the array of the parent to locate the child
+pmFPAview *pmFPAviewGenerate(const pmFPA *fpa, ///< FPA of interest
+                             const pmChip *chip, ///< Chip of interest, or NULL
+                             const pmCell *cell, ///< Cell of interest, or NULL
+                             const pmReadout *reaodut ///< Readout of interest, or NULL
+    );
+
+
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDU.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDU.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDU.c	(revision 22290)
@@ -0,0 +1,238 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Move to the appropriate extension in FITS file for HDU
+static bool hduMove(pmHDU *hdu,         // HDU with extname
+                    psFits *fits        // FITS file in which to move
+                   )
+{
+    // Deal with the PHU case
+    if (hdu->blankPHU || !hdu->extname) {
+        if (!psFitsMoveExtNum(fits, 0, false)) {
+            psError(PS_ERR_IO, false, "Unable to move to primary header!\n");
+            return false;
+        }
+        return true;
+    }
+
+    if (!psFitsMoveExtName(fits, hdu->extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", hdu->extname);
+        return false;
+    }
+
+    return true;
+}
+
+static void hduFree(pmHDU *hdu)
+{
+    psFree(hdu->extname);
+    psFree(hdu->format);
+    psFree(hdu->header);
+    psFree(hdu->images);
+    psFree(hdu->weights);
+    psFree(hdu->masks);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmHDU *pmHDUAlloc(const char *extname)
+{
+    pmHDU *hdu = psAlloc(sizeof(pmHDU));
+    psMemSetDeallocator(hdu, (psFreeFunc)hduFree);
+
+    if (!extname || strlen(extname) == 0) {
+        hdu->blankPHU = true;
+        hdu->extname = NULL;
+    } else {
+        hdu->blankPHU = false;
+        hdu->extname = psStringCopy(extname);
+    }
+    hdu->format  = NULL;
+    hdu->header  = NULL;
+    hdu->images  = NULL;
+    hdu->weights = NULL;
+    hdu->masks   = NULL;
+
+    return hdu;
+}
+
+bool psMemCheckHDU(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) hduFree);
+}
+
+
+bool pmHDUReadHeader(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    // Move to the appropriate extension
+    psTrace("psModules.camera", 5, "Moving to extension %s...\n", hdu->extname);
+    if (!hduMove(hdu, fits)) {
+        return false;
+    }
+
+    if (!hdu->header) {
+        psTrace("psModules.camera", 5, "Reading the header...\n");
+        hdu->header = psFitsReadHeader(hdu->header, fits);
+        if (! hdu->header) {
+            psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Read an HDU from a FITS file
+// XXX: Add a region specifier?
+bool hduRead(pmHDU *hdu,                // HDU to write
+             psArray **images,          // Images into which to read
+             psFits *fits               // FITS file to read
+            )
+{
+    assert(hdu);
+    assert(images);
+    assert(fits);
+
+    // Read the header; includes the move
+    if (!pmHDUReadHeader(hdu, fits)) {
+        return false;
+    }
+
+    if (hdu->blankPHU) {
+        // Done already!
+        return true;
+    }
+
+    if (*images) {
+        psWarning("HDU %s has already been read --- overwriting.\n", hdu->extname);
+        psFree(*images);                // Blow away anything existing
+    }
+    psTrace("psModules.camera", 5, "Reading the pixels...\n");
+    *images = psFitsReadImageCube(fits, psRegionSet(0,0,0,0));
+    if (!*images) {
+        psError(PS_ERR_IO, false, "Unable to read pixels for extension %s\n", hdu->extname);
+        return false;
+    }
+    return true;
+}
+
+bool pmHDURead(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduRead(hdu, &hdu->images, fits);
+}
+
+bool pmHDUReadMask(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduRead(hdu, &hdu->masks, fits);
+}
+
+bool pmHDUReadWeight(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduRead(hdu, &hdu->weights, fits);
+}
+
+// Write an HDU to a FITS file
+static bool hduWrite(pmHDU *hdu,        // HDU to write
+                     psArray *images,   // Images to write
+                     psFits *fits       // FITS file to which to write
+                    )
+{
+    assert(hdu);
+    assert(fits);
+
+    psTrace("psModules.camera", 7, "Writing HDU %s\n", hdu->extname);
+
+    if (!images && !hdu->header) {
+        psWarning("Nothing to write for HDU %s\n", hdu->extname);
+        return false;
+    }
+
+    // Preserve the extension name, if it's the PHU
+    char *extname = hdu->extname;       // The name of the extension
+    if (!extname && hdu->header) {
+        bool mdok = true;               // Status of MD lookup
+        extname = psMetadataLookupStr(&mdok, hdu->header, "EXTNAME");
+        if (!mdok || !extname || strlen(extname) == 0) {
+            extname = "";
+        }
+    }
+
+    // Make sure it's recognisable as what it's supposed to be
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+    if (!pmConfigConformHeader(hdu->header, hdu->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header to format.\n");
+        return false;
+    }
+
+    // Only a header
+    if (!images && !psFitsWriteBlank(fits, hdu->header, extname)) {
+        psError(PS_ERR_IO, false, "Unable to write header for extension %s\n", extname);
+        return false;
+    }
+
+    if (images) {
+        psTrace("psModules.camera", 9, "Writing pixels for %s\n", hdu->extname);
+        if (!psFitsWriteImageCube(fits, hdu->header, images, extname)) {
+            psError(PS_ERR_IO, false, "Unable to write image to extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    return true;
+}
+
+// XXX: Add a region specifier?
+bool pmHDUWrite(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduWrite(hdu, hdu->images, fits);
+}
+
+bool pmHDUWriteMask(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduWrite(hdu, hdu->masks, fits);
+}
+
+bool pmHDUWriteWeight(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduWrite(hdu, hdu->weights, fits);
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDU.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDU.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDU.h	(revision 22290)
@@ -0,0 +1,82 @@
+/* @file pmHDU.h
+ * @brief Define a header data unit (from a FITS file), with functions to read and write
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-01-02 20:33:41 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_HDU_H
+#define PM_HDU_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// An instance of the FITS Header Data Unit
+///
+/// Of course, it is not an exact replica of a FITS HDU --- they have no mask and weight data, but these are
+/// stored here for convenience --- it keeps all the relevant data about the image in one place.
+typedef struct
+{
+    psString extname;                   ///< The extension name
+    bool blankPHU;                      ///< Is this a blank FITS Primary Header Unit, i.e., no data?
+    psMetadata *format;                 ///< The camera format
+    psMetadata *header;                 ///< The FITS header, or NULL if primary for FITS; or section info
+    psArray *images;                    ///< The pixel data
+    psArray *weights;                   ///< The pixel data
+    psArray *masks;                     ///< The pixel data
+}
+pmHDU;
+
+
+/// Allocator for pmHDU
+pmHDU *pmHDUAlloc(const char *extname);   ///< Extension name, or NULL for PHU
+bool psMemCheckHDU(psPtr ptr);
+
+/// Read the HDU header only
+///
+/// Moves to the appropriate extension
+bool pmHDUReadHeader(pmHDU *hdu,        ///< HDU for which to read header
+                     psFits *fits       ///< FITS file from which to read
+                    );
+
+/// Read the HDU header and pixels
+///
+/// Moves to the appropriate extension
+bool pmHDURead(pmHDU *hdu,              ///< HDU to read
+               psFits *fits             ///< FITS file to read from
+              );
+
+/// Read the HDU header and mask
+///
+/// Moves to the appropriate extension
+bool pmHDUReadMask(pmHDU *hdu,          ///< HDU to read
+                   psFits *fits         ///< FITS file to read from
+                  );
+
+/// Read the HDU header and weight map
+///
+/// Moves to the appropriate extension
+bool pmHDUReadWeight(pmHDU *hdu,        ///< HDU to read
+                     psFits *fits       ///< FITS file to read from
+                    );
+
+/// Write the HDU header and pixels
+bool pmHDUWrite(pmHDU *hdu,             ///< HDU to write
+                psFits *fits            ///< FITS file to write to
+               );
+
+/// Write the HDU header and mask
+bool pmHDUWriteMask(pmHDU *hdu,         ///< HDU to write
+                    psFits *fits        ///< FITS file to write to
+                   );
+
+/// Write the HDU header and weight map
+bool pmHDUWriteWeight(pmHDU *hdu,       ///< HDU to write
+                      psFits *fits      ///< FITS file to write to
+                     );
+
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUGenerate.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUGenerate.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUGenerate.c	(revision 22290)
@@ -0,0 +1,710 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsStandard.h"
+#include "pmHDUGenerate.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Add cells in a chip to a list
+static bool addCellsFromChip(psList *list, // List of cells
+                             const pmChip *chip // The chip from which to add cells
+                            )
+{
+    assert(list);
+    assert(chip);
+
+    psArray *cells = chip->cells;       // Array of cells
+    bool result = true;                 // Result of adding cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // A cell
+        if (!cell->hdu) {               // Don't add cells that have their own HDU
+            result |= psListAdd(list, PS_LIST_TAIL, cell);
+        }
+    }
+
+    return result;
+}
+
+// Add cells in an FPA to a list
+static bool addCellsFromFPA(psList *list, // List of cells
+                            const pmFPA *fpa // The FPA from which to add cells
+                           )
+{
+    assert(list);
+    assert(fpa);
+
+    psArray *chips = fpa->chips;        // Array of chips
+    bool result = true;                 // Result of adding cells
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // A chip
+        if (! chip->hdu) {              // Don't add chips that have their own HDU
+            result |= addCellsFromChip(list, chip);
+        }
+    }
+
+    return result;
+}
+
+// Get the maximum extent of the HDU from the trimsec and biassecs
+static bool sizeHDU(int *xSize, int *ySize, // Size of HDU
+                    psList *cells       // List of cells
+                   )
+{
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    pmCell *cell = NULL;                // The cell from iteration
+    bool mdok = true;                   // Status of MD lookup
+    *xSize = 0;
+    *ySize = 0;
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (mdok && trimsec && !psRegionIsNaN(*trimsec)) {
+            *xSize = PS_MAX(trimsec->x1, *xSize);
+            *ySize = PS_MAX(trimsec->y1, *ySize);
+        } else {
+            psFree(cellsIter);
+            return false;
+        }
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (mdok && biassecs) {
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+            psRegion *biassec = NULL;   // The bias section
+            while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                if (!psRegionIsNaN(*biassec)) {
+                    *xSize = PS_MAX(biassec->x1, *xSize);
+                    *ySize = PS_MAX(biassec->y1, *ySize);
+                } else {
+                    psFree(biassecsIter);
+                    psFree(cellsIter);
+                    return false;
+                }
+            }
+            psFree(biassecsIter);
+        }
+    }
+    psFree(cellsIter);
+
+    return (*xSize != 0 && *ySize != 0);
+}
+
+
+static psRegion *sectionForImage(int *position, // Position on the output image, updated
+                                 const psImage *image, // Image containing the sizes and offsets
+                                 int readdir // Read direction, 1=rows, 2=cols
+                                )
+{
+    psRegion *region = NULL;            // The region to return
+    switch (readdir) {
+    case 1:                           // Read direction is rows
+        region = psRegionAlloc(*position, *position + image->numCols, image->row0,
+                               image->row0 + image->numRows);
+        *position += image->numCols;
+        break;
+    case 2:                           // Read direction is columns
+        region = psRegionAlloc(image->col0, image->col0 + image->numCols, *position,
+                               *position + image->numRows);
+        *position += image->numRows;
+        break;
+    default:
+        psAbort("Shouldn't ever get here!\n");
+    }
+
+    return region;
+}
+
+static bool doBiasSections(int *position, // Position on the output image, updated
+                           int *readdir,// Read direction for cells
+                           pmCell *cell // Cell
+                          )
+{
+    psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!biassecItem) {
+        psWarning("CELL.BIASSEC has not been initialised in cell --- ignored.\n");
+        return false;
+    }
+    psFree(biassecItem->data.V);        // Blow away the old list
+    psList *biassecs = psListAlloc(NULL);
+    biassecItem->data.V = biassecs;
+
+    bool mdok = true;                   // Status of MD lookup
+    int cellreaddir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || (cellreaddir != 1 && cellreaddir != 2)) {
+        // Probably unnecessary, but just in case....
+        psWarning("CELL.READDIR is not set in cell --- ignored.\n");
+        return false;
+    }
+    if (*readdir == 0) {
+        *readdir = cellreaddir;
+    } else if (*readdir != cellreaddir) {
+        psWarning("CELL.READDIR does not match read direction for HDU --- ignored.\n");
+        return false;
+    }
+
+    pmReadout *readout = cell->readouts->data[0]; // The first readout, as representative
+    psList *biases = readout->bias; // The bias images from the readout
+
+    psListIterator *biasIter = psListIteratorAlloc(biases, PS_LIST_HEAD, true); // Iterator for biases
+    psImage *bias = NULL;       // Bias image from iteration
+    while ((bias = psListGetAndIncrement(biasIter))) {
+        // Construct a region
+        psRegion *biassec = sectionForImage(position, bias, *readdir);
+        psListAdd(biassecs, PS_LIST_TAIL, biassec);
+        psFree(biassec);        // Drop reference
+    }
+    psFree(biasIter);
+
+    return true;
+}
+
+// Attempt to read CELL.TRIMSEC and CELL.BIASSEC from the cell format if they are specified by VALUE
+static bool readTrimBias(psList *cells // List of cells below the HDU
+    )
+{
+    bool mdok;                          // Status of MD lookup
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    pmCell *cell = NULL;                // Cell from iteration
+    bool allFixed = true;               // We're able to fix all TRIMSEC and BIASSEC
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC"); // Item with trimsec
+        if (!trimsecItem || trimsecItem->type != PS_DATA_REGION) {
+            psWarning("CELL.TRIMSEC has not been initialised in cell --- ignored.\n");
+            return false;
+        }
+        psRegion *trimsec = trimsecItem->data.V; // Trim section
+        if (!trimsec || psRegionIsNaN(*trimsec)) {
+            const char *trimsecSource = psMetadataLookupStr(&mdok, cell->config, "CELL.TRIMSEC.SOURCE");
+            if (strcmp(trimsecSource, "VALUE") == 0) {
+
+                const char *trimsecStr = psMetadataLookupStr(&mdok, cell->config, "CELL.TRIMSEC");
+                if (!trimsec) {
+                    trimsec = trimsecItem->data.V = psRegionAlloc(NAN, NAN, NAN, NAN);
+                }
+                *trimsec = psRegionFromString(trimsecStr);
+            } else {
+                allFixed = false;
+            }
+        }
+
+        psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (!biassecItem) {
+            psWarning("CELL.BIASSEC has not been initialised in cell --- ignored.\n");
+            return false;
+        }
+        psList *biassecs = biassecItem->data.V;
+        if (!biassecs || biassecs->n != 0) {
+            allFixed = false;
+            continue;
+        }
+
+        const char *biassecSource = psMetadataLookupStr(&mdok, cell->config, "CELL.BIASSEC.SOURCE");
+        if (strcmp(biassecSource, "VALUE") == 0) {
+            const char *biassecStr = psMetadataLookupStr(&mdok, cell->config, "CELL.BIASSEC");
+            psFree(biassecItem->data.V);
+            biassecItem->data.V = p_pmConceptParseRegions(biassecStr);
+        } else {
+            allFixed = false;
+        }
+    }
+    psFree(cellsIter);
+
+    return allFixed;
+}
+
+
+// Generate CELL.TRIMSEC and CELL.BIASSEC for the cells
+static bool generateTrimBias(psList *cells // List of cells below the HDU
+                            )
+{
+    pmCell *cell = NULL;                // Cell from iteration
+    int numCells = cells->n;            // Number of cells
+    int cellNum = 0;                    // The cell number
+    int position = 0;                   // Position on the image
+    bool mdok = true;                   // Status of MD lookup
+    int readdir = 0;                    // Read direction (1=rows, 2=cols)
+
+    // First run through to do the LHS biases
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    bool done = false;                  // Done with iteration (due to being halfway through)?
+    while ((cell = psListGetAndIncrement(cellsIter)) && !done) {
+        if (cellNum <= numCells/2 - 1) {
+            doBiasSections(&position, &readdir, cell);
+            cellNum++;
+        } else {
+            done = true;
+        }
+    }
+
+    // Second run through to do the trim sections
+    psListIteratorSet(cellsIter, PS_LIST_HEAD);
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC"); // Item with trimsec
+        if (!trimsecItem || trimsecItem->type != PS_DATA_REGION) {
+            psWarning("CELL.TRIMSEC has not been initialised in cell --- ignored.\n");
+            continue;
+        }
+        psRegion *trimsec = trimsecItem->data.V; // Trim section
+
+        int cellreaddir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+        if (!mdok || (cellreaddir != 1 && cellreaddir != 2)) {
+            // Probably unnecessary, but just in case....
+            psWarning("CELL.READDIR is not set in cell --- ignored.\n");
+            continue;
+        }
+        if (readdir == 0 && mdok && cellreaddir != 0) {
+            readdir = cellreaddir;
+        } else if (readdir != cellreaddir) {
+            psWarning("CELL.READDIR for cells within the HDU do not match!\n");
+            cellreaddir = readdir;
+        }
+
+        pmReadout *readout = cell->readouts->data[0]; // The first readout, as representative
+        // The proper image, used to get the size
+        psImage *image = readout->image ? readout->image : (readout->mask ? readout->mask : readout->weight);
+        if (!image) {
+            continue;
+        }
+        if (readout->mask &&
+                (readout->mask->numCols != image->numCols || readout->mask->numRows != image->numRows)) {
+            psWarning("Image and mask have different sizes (%dx%d vs %dx%d)!\n",
+                     image->numCols, image->numRows, readout->mask->numCols, readout->mask->numRows);
+        }
+        if (readout->weight &&
+                (readout->weight->numCols != image->numCols || readout->weight->numRows != image->numRows)) {
+            psWarning("Image and weight have different sizes (%dx%d vs %dx%d)!\n",
+                     image->numCols, image->numRows, readout->weight->numCols, readout->weight->numRows);
+        }
+        // New reference
+        trimsec = sectionForImage(&position, image, cellreaddir);
+        psFree(trimsecItem->data.V);
+        trimsecItem->data.V = trimsec;
+    }
+
+    // A final run through to do the RHS biases
+    psListIteratorSet(cellsIter, cellNum);
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        doBiasSections(&position, &readdir, cell);
+    }
+
+    // Clean up
+    psFree(cellsIter);
+
+    return (position > 0);
+}
+
+// Check the type for a current image against a previous type
+static psElemType checkTypes(psElemType previous, // Previously defined type, or 0
+                             psElemType current // Current type
+                            )
+{
+    if (previous == 0) {
+        return current;
+    }
+
+    if (previous != current) {
+        psWarning("Images within the HDU are of different types (%x vs %x) --- promoting\n",
+                  previous, current);
+        return PS_MAX(previous, current);
+    }
+
+    return previous;
+}
+
+
+// Paste the source image into the target, according to the provided region.  The source is then updated to
+// reference the region within the target.
+static psImage *pasteImage(psImage *target, // Target image, into which the paste is made
+                           psImage *source,// Source image, from which the paste is made, and then changed
+                           psRegion *region // Image section into which to paste
+                          )
+{
+    if (source->numCols != region->x1 - region->x0 || source->numRows != region->y1 - region->y0) {
+        psString regionString = psRegionToString(*region);
+        psWarning("Image size (%dx%d) does not match region (%s).\n",
+                 source->numCols, source->numRows, regionString);
+        psFree(regionString);
+    }
+    psImageOverlaySection(target, source, region->x0, region->y0, "=");
+
+    // Reference the HDU version, so that subsequent changes will touch the HDU
+    return psImageSubset(target, *region);
+}
+
+
+// Generate the HDU, given a list of cells below that HDU.  This is the main engine function, that does all
+// the work.
+static bool generateHDU(pmHDU *hdu,     // HDU to generate
+                        psList *cells   // List of cells
+                       )
+{
+    // Check the number of readouts is consistent within the HDU
+    int numReadouts = -1;               // Number of readouts
+    psElemType imageType = 0;           // Type of readout images
+    psElemType maskType = 0;            // Type of readout masks
+    psElemType weightType = 0;          // Type of readout weights
+    {
+        psListIterator *iter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+        pmCell *cell = NULL;                // The cell from iteration
+        while ((cell = psListGetAndIncrement(iter)))
+        {
+            psArray *readouts = cell->readouts;
+            if (numReadouts == -1) {
+                numReadouts = readouts->n;
+            } else if (readouts->n != numReadouts) {
+                psError(PS_ERR_IO, true, "Number of readouts doesn't match: %ld vs %d\n", readouts->n,
+                        numReadouts);
+                return false;
+            }
+            for (int i = 0; i < numReadouts; i++) {
+                pmReadout *readout = readouts->data[i]; // The readout
+                if (!readout) {
+                    continue;
+                }
+
+                if (!hdu->images && readout->image) {
+                    imageType = checkTypes(imageType, readout->image->type.type);
+                }
+                if (!hdu->masks && readout->mask) {
+                    maskType = checkTypes(maskType, readout->mask->type.type);
+                }
+                if (!hdu->weights && readout->weight) {
+                    weightType = checkTypes(weightType, readout->weight->type.type);
+                }
+            }
+        }
+        psFree(iter);
+    }
+    if (numReadouts == 0 || (imageType == 0 && maskType == 0 && weightType == 0)) {
+        // Nothing from which to create an HDU
+        psFree(cells);
+        return false;
+    }
+
+    // Get the size of the HDU, either from existing trimsec and biassec, or generate these and try again
+    int xSize = 0, ySize = 0;           // Size of HDU
+    if (!sizeHDU(&xSize, &ySize, cells) &&
+        !(readTrimBias(cells) && sizeHDU(&xSize, &ySize, cells)) &&
+        !(generateTrimBias(cells) && sizeHDU(&xSize, &ySize, cells))) {
+        psError(PS_ERR_IO, true, "Unable to determine size of HDU!\n");
+        return false;
+    }
+
+    // Generate the HDU
+    if (imageType) {
+        hdu->images = psArrayAlloc(numReadouts);
+        for (int i = 0; i < numReadouts; i++) {
+            psImage *image = psImageAlloc(xSize, ySize, imageType);
+            psImageInit(image, 0.0);
+            hdu->images->data[i] = image;
+        }
+    }
+    if (maskType) {
+        hdu->masks = psArrayAlloc(numReadouts);
+        for (int i = 0; i < numReadouts; i++) {
+            psImage *mask = psImageAlloc(xSize, ySize, maskType);
+            psImageInit(mask, 0);
+            hdu->masks->data[i] = mask;
+        }
+    }
+    if (weightType) {
+        hdu->weights = psArrayAlloc(numReadouts);
+        for (int i = 0; i < numReadouts; i++) {
+            psImage *weight = psImageAlloc(xSize, ySize, weightType);
+            psImageInit(weight, 0.0);
+            hdu->weights->data[i] = weight;
+        }
+    }
+
+    // Insert the pixels into the HDU
+    {
+        psListIterator *iter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+        pmCell *cell = NULL;           // The cell from iteration
+        bool mdok = true;               // Result of MD lookup
+        while ((cell = psListGetAndIncrement(iter)))
+        {
+            psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+            if (!mdok || !trimsec) {
+                psAbort("Shouldn't ever get here --- CELL.TRIMSEC should have been set above.\n");
+            }
+            psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias secionts
+            if (!mdok || !biassecs) {
+                psAbort("Shouldn't ever get here --- CELL.BIASSEC should have been set above.\n");
+            }
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+
+            psArray *readouts = cell->readouts; // Array of readouts
+            psArray *hduImages = hdu->images; // Array of images in the HDU
+            psArray *hduMasks = hdu->masks; // Array of masks in the HDU
+            psArray *hduWeights = hdu->weights; // Array of weights in the HDU
+            for (int i = 0; i < readouts->n; i++) {
+                pmReadout *readout = readouts->data[i]; // The readout of interest
+                if (!readout) {
+                    continue;
+                }
+
+                if (readout->image) {
+                    psImage *new = pasteImage(hduImages->data[i], readout->image, trimsec);
+                    psFree(readout->image);
+                    readout->image = new;
+                }
+                if (readout->mask) {
+                    psImage *new = pasteImage(hduMasks->data[i], readout->mask, trimsec);
+                    psFree(readout->mask);
+                    readout->mask = new;
+                }
+                if (readout->weight) {
+                    psImage *new = pasteImage(hduWeights->data[i], readout->weight, trimsec);
+                    psFree(readout->weight);
+                    readout->weight = new;
+                }
+
+                if (biassecs->n != readout->bias->n) {
+                    psWarning("Number of bias sections (%ld) and number of biases (%ld) do not match.\n",
+                              biassecs->n, readout->bias->n);
+                }
+                psListIterator *biasIter = psListIteratorAlloc(readout->bias, PS_LIST_HEAD, false); // Iteratr
+                psImage *bias = NULL;   // Bias image, from iteration
+                psListIteratorSet(biassecsIter, PS_LIST_HEAD);
+                psRegion *biassec = NULL; // Bias region, from iteration
+                psList *newBias = psListAlloc(NULL); // New list of bias images
+                while ((bias = psListGetAndIncrement(biasIter)) &&
+                        (biassec = psListGetAndIncrement(biassecsIter))) {
+                    psImage *new = pasteImage(hduImages->data[i], bias, biassec);
+                    psListAdd(newBias, PS_LIST_TAIL, new);
+                    psFree(new);        // Drop reference
+                }
+                psFree(biasIter);
+
+                // Add on the new list of bias images
+                psFree(readout->bias);
+                readout->bias = newBias;
+            }
+            psFree(biassecsIter);
+        } // Iterating over cells within the HDU
+        psFree(iter);
+    }
+    psFree(cells);
+
+    return true;
+}
+
+// Return the level that an extension applies to
+static pmFPALevel extensionLevel(pmHDU *hdu // HDU to check
+                                )
+{
+    if (!hdu->format) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "HDU does not have a camera format.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *file = psMetadataLookupMetadata(&mdok, hdu->format, "FILE"); // File information for camera format
+    if (!mdok || !file) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't file FILE information for camera format "
+                "configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    psString extensions = psMetadataLookupStr(&mdok, file, "EXTENSIONS"); // Where the HDUs are
+    if (!mdok || !extensions || strlen(extensions) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find EXTENSIONS in the FILE information of the camera "
+                "format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    if (strcasecmp(extensions, "CELL") == 0) {
+        return PM_FPA_LEVEL_CELL;
+    }
+    if (strcasecmp(extensions, "CHIP") == 0) {
+        return PM_FPA_LEVEL_CHIP;
+    }
+    if (strcasecmp(extensions, "FPA") == 0) {
+        return PM_FPA_LEVEL_FPA;
+    }
+    if (strcasecmp(extensions, "NONE") == 0) {
+        return PM_FPA_LEVEL_NONE;
+    }
+    // No idea what it is
+    psError(PS_ERR_IO, true, "EXTENSIONS (%s) in FILE information is not FPA, CHIP, CELL or NONE.\n",
+            extensions);
+    return PM_FPA_LEVEL_NONE;
+}
+
+// Generate HDU for cells belonging to a chip --- just an iterator
+static bool generateForCells(pmChip *chip // Chip for which to generate HDUs
+                            )
+{
+    psArray *cells = chip->cells;       // Array of cells
+    bool status = true;                 // Status of HDU generation
+    for (long i = 0; i < cells->n; i++) {
+        status |= pmHDUGenerateForCell(cells->data[i]);
+    }
+    return status;
+}
+
+// Generate HDU for chips belonging to an FPA --- just an iterator
+static bool generateForChips(pmFPA *fpa // FPA for which to generate HDUs
+                            )
+{
+    psArray *chips = fpa->chips;        // Array of chips
+    bool status = true;                 // Status of HDU generation
+    for (long i = 0; i < chips->n; i++) {
+        status |= pmHDUGenerateForChip(chips->data[i]);
+    }
+    return status;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmHDUGenerateForCell(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromCell(cell); // The HDU in the cell
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find an HDU for cell.\n");
+        return false;
+    }
+    if (hdu->images && hdu->masks && hdu->weights) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_CELL: {
+            psList *cells = psListAlloc(NULL); // List of cells below the HDU
+
+            if (cell->hdu) {
+                psListAdd(cells, PS_LIST_TAIL, cell);
+            } else {
+                pmChip *chip = cell->parent;    // The parent chip
+                if (chip->hdu) {
+                    addCellsFromChip(cells, chip);
+                } else {
+                    pmFPA *fpa = chip->parent;  // The parent FPA
+                    if (fpa->hdu) {
+                        addCellsFromFPA(cells, fpa);
+                    }
+                }
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    case PM_FPA_LEVEL_CHIP:
+    case PM_FPA_LEVEL_FPA:
+        return pmHDUGenerateForChip(cell->parent);
+    default:
+        psAbort("Shouldn't ever get here: check your camera format configuration.\n");
+    }
+    return false;
+}
+
+bool pmHDUGenerateForChip(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromChip(chip);   // The HDU in the chip
+    if (!hdu) {
+        // Nothing here; need to look further down
+        return generateForCells(chip);
+    }
+    if (hdu->images && hdu->masks && hdu->weights) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_CELL:
+        // Work on lower levels
+        return generateForCells(chip);
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_CHIP: {
+            // Work on this level
+            psList *cells = psListAlloc(NULL);  // List of cells below the HDU
+            if (chip->hdu) {
+                addCellsFromChip(cells, chip);
+            } else {
+                pmFPA *fpa = chip->parent;  // The parent FPA
+                if (fpa->hdu) {
+                    addCellsFromFPA(cells, fpa);
+                }
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    case PM_FPA_LEVEL_FPA:
+        // Work on higher levels
+        return pmHDUGenerateForFPA(chip->parent);
+    default:
+        psAbort("Shouldn't ever get here: check your camera format configuration.\n");
+    }
+    return false;
+}
+
+
+bool pmHDUGenerateForFPA(pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromFPA(fpa);     // The HDU in the FPA
+    if (!hdu) {
+        // Nothing here; need to look further down
+        return generateForChips(fpa);
+    }
+    if (hdu->images && hdu->masks && hdu->weights) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_CELL:
+    case PM_FPA_LEVEL_CHIP:
+        // Work on lower levels
+        return generateForChips(fpa);
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_FPA: {
+            // Work on this level
+            psList *cells = psListAlloc(NULL); // List of cells below the HDU
+            if (fpa->hdu) {
+                addCellsFromFPA(cells, fpa);
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    default:
+        psAbort("Shouldn't ever get here: check your camera format configuration.\n");
+    }
+    return false;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUGenerate.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUGenerate.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUGenerate.h	(revision 22290)
@@ -0,0 +1,53 @@
+/* @file pmHDUGenerate.h
+ * @brief Generate HDU pixels from FPA components that have pixels
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_HDU_GENERATE_H
+#define PM_HDU_GENERATE_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Generate an HDU (with CELL.TRIMSEC, CELL.BIASSEC and pixels) for a cell with pixels
+///
+/// The write functions for the FPA hierarchy use pmHDUWrite, which assumes that the images in the readouts
+/// are subimages of the pixels in the HDU structure.  If this is not the case, the HDU pixels can be
+/// generated using some simple assumptions.  Splices the images and overscans together without regard for
+/// CELL.X0 and CELL.Y0 (for a proper mosaic, see pmFPAMosaic), though it should respect CELL.READDIR (so that
+/// the bias and trim sections match properly).  A warning may be generated after running this function if the
+/// bias and trim sections are specified in the camera format by default values rather than in the header.
+/// Failure of this function is often due to a bad camera format file.
+bool pmHDUGenerateForCell(pmCell *cell  ///< The cell for which to generate an HDU
+                         );
+
+/// Generate an HDU (with CELL.TRIMSEC, CELL.BIASSEC and pixels) for a cell with pixels
+///
+/// The write functions for the FPA hierarchy use pmHDUWrite, which assumes that the images in the readouts
+/// are subimages of the pixels in the HDU structure.  If this is not the case, the HDU pixels can be
+/// generated using some simple assumptions.  Splices the images and overscans together without regard for
+/// CELL.X0 and CELL.Y0 (for a proper mosaic, see pmFPAMosaic), though it should respect CELL.READDIR (so that
+/// the bias and trim sections match properly).  A warning may be generated after running this function if the
+/// bias and trim sections are specified in the camera format by default values rather than in the header.
+/// Failure of this function is often due to a bad camera format file.
+bool pmHDUGenerateForChip(pmChip *chip  ///< The chip for which to generate an HDU
+                         );
+
+// Generate an HDU (with CELL.TRIMSEC, CELL.BIASSEC and pixels) from an FPA with pixels
+///
+/// The write functions for the FPA hierarchy use pmHDUWrite, which assumes that the images in the readouts
+/// are subimages of the pixels in the HDU structure.  If this is not the case, the HDU pixels can be
+/// generated using some simple assumptions.  Splices the images and overscans together without regard for
+/// CELL.X0 and CELL.Y0 (for a proper mosaic, see pmFPAMosaic), though it should respect CELL.READDIR (so that
+/// the bias and trim sections match properly).  A warning may be generated after running this function if the
+/// bias and trim sections are specified in the camera format by default values rather than in the header.
+/// Failure of this function is often due to a bad camera format file.
+bool pmHDUGenerateForFPA(pmFPA *fpa     ///< The fpa for which to generate an HDU
+                        );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUUtils.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUUtils.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUUtils.c	(revision 22290)
@@ -0,0 +1,151 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+
+pmHDU *pmHDUFromFPA(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    return fpa->hdu;
+}
+
+pmHDU *pmHDUFromChip(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    pmHDU *hdu = chip->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromFPA(chip->parent); // Grab HDU info from the FPA
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromCell(const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    pmHDU *hdu = cell->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromChip(cell->parent); // Grab HDU info from the chip
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromReadout(const pmReadout *readout)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+
+    pmCell *cell = readout->parent; // cell containing this readout;
+    pmHDU *hdu = pmHDUFromCell(cell);
+    return hdu;
+}
+
+// Get the lowest HDU
+pmHDU *pmHDUGetLowest(const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    pmHDU *hdu = NULL;          // The HDU that's at the lowest level
+    if (cell) {
+        hdu = pmHDUFromCell(cell);
+    } else if (chip) {
+        hdu = pmHDUFromChip(chip);
+    } else if (fpa) {
+        hdu = pmHDUFromFPA(fpa);
+    }
+
+    return hdu;
+}
+
+// Get the highest HDU
+pmHDU *pmHDUGetHighest(const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    pmHDU *hdu = NULL;          // The HDU that's at the highest level
+    if (fpa) {
+        hdu = pmHDUFromFPA(fpa);
+    }
+    if (!hdu && chip) {
+        hdu = pmHDUFromChip(chip);
+    }
+    if (!hdu && cell) {
+        hdu = pmHDUFromCell(cell);
+    }
+
+    return hdu;
+}
+
+// Print spaces to indent
+#define INDENT(FILE, LEVEL) \
+{ \
+    for (int i = 0; i < (LEVEL); i++) { \
+        fprintf(FILE, " "); \
+    } \
+}
+
+void pmHDUPrint(FILE *fd, const pmHDU *hdu, int level, bool header)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu,);
+
+    INDENT(fd, level);
+    if (hdu->blankPHU) {
+        fprintf(fd, "HDU: (PHU)\n");
+    } else {
+        fprintf(fd, "HDU: %s\n", hdu->extname);
+    }
+
+    INDENT(fd, level + 1);
+    fprintf(fd, "Format: %p\n", hdu->format);
+    if (header) {
+        INDENT(fd, level + 1);
+        if (hdu->header) {
+            fprintf(fd, "Header:\n");
+            psMetadataPrint(fd, hdu->header, level + 2);
+        } else {
+            fprintf(fd, "No header.\n");
+        }
+    }
+
+    INDENT(fd, level + 1);
+    if (hdu->images) {
+        fprintf(fd, "Images:\n");
+        for (long i = 0; i < hdu->images->n; i++) {
+            psImage *image = hdu->images->data[i]; // Image of interest
+            INDENT(fd, level + 2);
+            fprintf(fd, "%ld: %dx%d\n", i, image->numCols, image->numRows);
+        }
+    } else {
+        fprintf(fd, "NO images.\n");
+    }
+
+    INDENT(fd, level + 1);
+    if (hdu->masks) {
+        fprintf(fd, "Masks:\n");
+        for (long i = 0; i < hdu->masks->n; i++) {
+            psImage *mask = hdu->masks->data[i]; // Mask of interest
+            INDENT(fd, level + 2);
+            fprintf(fd, "%ld: %dx%d\n", i, mask->numCols, mask->numRows);
+        }
+    } else {
+        fprintf(fd, "NO masks.\n");
+    }
+
+    INDENT(fd, level + 1);
+    if (hdu->weights) {
+        fprintf(fd, "Weights:\n");
+        for (long i = 0; i < hdu->weights->n; i++) {
+            psImage *weight = hdu->weights->data[i]; // Weight image of interest
+            INDENT(fd, level + 2);
+            fprintf(fd, "%ld: %dx%d\n", i, weight->numCols, weight->numRows);
+        }
+    } else {
+        fprintf(fd, "NO weights.\n");
+    }
+
+    return;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUUtils.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUUtils.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmHDUUtils.h	(revision 22290)
@@ -0,0 +1,58 @@
+/* @file pmHDUUtils.h
+ * @brief Utility functions for working with an HDU
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_HDU_UTILS_H
+#define PM_HDU_UTILS_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Get the lowest HDU in the hierarchy
+///
+/// The lowest HDU in the hierarchy will be the one with the actual pixels (if all levels are supplied).
+pmHDU *pmHDUGetLowest(const pmFPA *fpa, ///< The FPA
+                      const pmChip *chip, ///< The chip, or NULL
+                      const pmCell *cell ///< The cell, or NULL
+                     );
+
+/// Get the highest HDU in the hierarchy
+///
+/// The highest HDU in the hierarchy will be the PHU (might get NULL if not all levels are supplied)
+pmHDU *pmHDUGetHighest(const pmFPA *fpa, ///< The FPA
+                       const pmChip *chip, ///< The chip, or NULL
+                       const pmCell *cell ///< The cell, or NULL
+                      );
+
+/// Given an FPA, return the HDU (or NULL if all HDUs reside below the FPA)
+pmHDU *pmHDUFromFPA(const pmFPA *fpa    ///< FPA for which to find HDU
+                   );
+
+/// Given a chip, return the HDU (or NULL if it resides below the chip)
+pmHDU *pmHDUFromChip(const pmChip *chip ///< Chip for which to find HDU
+                    );
+
+/// Given a cell, return the HDU
+pmHDU *pmHDUFromCell(const pmCell *cell ///< Cell for which to find HDU
+                    );
+
+/// Given a readout, return the HDU
+pmHDU *pmHDUFromReadout(const pmReadout *readout ///< Readout for which to find HDU
+                       );
+
+/// Print details about an HDU
+///
+/// This is intended for testing or development use.
+void pmHDUPrint(FILE *fd,               ///< File descriptor to which to print
+                const pmHDU *hdu,       ///< HDU to print
+                int level,              ///< Level at which to print
+                bool header             ///< Print header?
+               );
+/// @}
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutFake.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutFake.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutFake.c	(revision 22290)
@@ -0,0 +1,159 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmModelClass.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmModelUtils.h"
+
+#include "pmReadoutFake.h"
+
+#define MODEL_TYPE "PS_MODEL_RGAUSS"    // Type of model to use
+#define MAX_AXIS_RATIO 20.0             // Maximum axis ratio for PSF model
+
+
+// Given an object model, circularise it by setting the axes to be identical
+static bool circulariseModel(pmModel *model // Model to circularise
+    )
+{
+    assert(model);
+
+    psF32 *params = model->params->data.F32; // Model parameters
+    psEllipseAxes axes = pmPSF_ModelToAxes(params, MAX_AXIS_RATIO); // Ellipse axes
+    // Curiously, the minor axis can be larger than the major axis, so need to check.
+    if (axes.major >= axes.minor) {
+        axes.minor = axes.major;
+    } else {
+        axes.major = axes.minor;
+    }
+    return pmPSF_AxesToModel(params, axes);
+}
+
+bool pmReadoutFakeFromSources(pmReadout *readout, int numCols, int numRows, const psArray *sources,
+                              const psVector *xOffset, const psVector *yOffset, const pmPSF *psf,
+                              float minFlux, int radius, bool circularise)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_INT_LARGER_THAN(numCols, 0, false);
+    PS_ASSERT_INT_LARGER_THAN(numRows, 0, false);
+    PS_ASSERT_ARRAY_NON_NULL(sources, false);
+
+    if (xOffset || yOffset) {
+        PS_ASSERT_VECTOR_NON_NULL(xOffset, false);
+        PS_ASSERT_VECTOR_NON_NULL(yOffset, false);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(xOffset, yOffset, false);
+        PS_ASSERT_VECTOR_TYPE(xOffset, PS_TYPE_S32, false);
+        PS_ASSERT_VECTOR_TYPE_EQUAL(xOffset, yOffset, false);
+        if (xOffset->n != sources->n) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Number of offset vectors (%ld) and sources (%ld) doesn't match",
+                    xOffset->n, sources->n);
+            return false;
+        }
+    }
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+    if (radius > 0 && isfinite(minFlux) && minFlux > 0.0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Cannot define both minimum flux and fixed radius.");
+        return false;
+    }
+
+    readout->image = psImageRecycle(readout->image, numCols, numRows, PS_TYPE_F32);
+    psImageInit(readout->image, 0);
+
+    int numSources = sources->n;          // Number of stars
+
+    pmModel *fakeModel = pmModelFromPSFforXY(psf, (float)numCols / 2.0, (float)numRows / 2.0,
+                                             1.0); // Fake model, with central intensity of 1.0
+
+    float flux0 = fakeModel->modelFlux(fakeModel->params); // Flux for central intensity of 1.0
+
+    if (circularise && !circulariseModel(fakeModel)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
+        psFree(fakeModel);
+        return false;
+    }
+    psFree(fakeModel);
+
+    for (int i = 0; i < numSources; i++) {
+        pmSource *source = sources->data[i]; // Source of interest
+        if (!isfinite(source->psfMag)) {
+            continue;
+        }
+        float x, y;                     // Coordinates of source
+        if (source->modelPSF) {
+            x = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+            y = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+        } else {
+            x = source->peak->xf;
+            y = source->peak->yf;
+        }
+
+        pmModel *fakeModel = pmModelFromPSFforXY(psf, x, y, powf(10.0, -0.4 * source->psfMag) / flux0);
+        if (!fakeModel) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate model for source %d (%.1f,%.1f)", i, x, y);
+            return false;
+        }
+        if (circularise && !circulariseModel(fakeModel)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
+            psFree(fakeModel);
+            return false;
+        }
+
+        psTrace("psModules.camera", 10, "Adding source at %f,%f with flux %f\n",
+                fakeModel->params->data.F32[PM_PAR_XPOS], fakeModel->params->data.F32[PM_PAR_YPOS],
+                fakeModel->params->data.F32[PM_PAR_I0]);
+
+        pmSource *fakeSource = pmSourceAlloc(); // Fake source to generate
+        fakeSource->peak = pmPeakAlloc(x, y, fakeModel->params->data.F32[PM_PAR_I0], PM_PEAK_LONE);
+        float fakeRadius = radius > 0 ? radius : fakeModel->modelRadius(fakeModel->params, minFlux); // Radius
+
+        if (xOffset) {
+            if (!pmSourceDefinePixels(fakeSource, readout, x + xOffset->data.S32[i],
+                                      y + yOffset->data.S32[i], fakeRadius)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to define pixels for source.");
+                psFree(readout);
+                psFree(fakeModel);
+                return false;
+            }
+            if (!pmModelAddWithOffset(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0,
+                                      - xOffset->data.S32[i], - yOffset->data.S32[i])) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to add model of source to image.");
+                psFree(readout);
+                psFree(fakeModel);
+                return false;
+            }
+        } else {
+            if (!pmSourceDefinePixels(fakeSource, readout, x, y, fakeRadius)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to define pixels for source.");
+                psFree(readout);
+                psFree(fakeModel);
+                return false;
+            }
+            if (!pmModelAdd(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to add model of source to image.");
+                psFree(readout);
+                psFree(fakeModel);
+                return false;
+            }
+        }
+        psFree(fakeSource);
+        psFree(fakeModel);
+    }
+
+    return true;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutFake.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutFake.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutFake.h	(revision 22290)
@@ -0,0 +1,26 @@
+#ifndef PM_READOUT_FAKE_H
+#define PM_READOUT_FAKE_H
+
+#include <pslib.h>
+#include <pmHDU.h>
+#include <pmFPA.h>
+
+#include <pmMoments.h>
+#include <pmResiduals.h>
+#include <pmGrowthCurve.h>
+#include <pmTrend2D.h>
+#include <pmPSF.h>
+
+/// Generate a fake readout from an array of sources
+bool pmReadoutFakeFromSources(pmReadout *readout, ///< Output readout, or NULL
+                              int numCols, int numRows, ///< Dimension of image
+                              const psArray *sources, ///< Array of pmSource
+                              const psVector *xOffset, ///< x offsets for sources (source -> img), or NULL
+                              const psVector *yOffset, ///< y offsets for sources (source -> img), or NULL
+                              const pmPSF *psf, ///< PSF for sources
+                              float minFlux, ///< Minimum flux to bother about; for setting source radius
+                              int radius, ///< Fixed radius for sources
+                              bool circularise ///< Circularise PSF model?
+    );
+
+#endif
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutStack.c
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutStack.c	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutStack.c	(revision 22290)
@@ -0,0 +1,132 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pmReadoutStack.h"
+
+
+bool pmReadoutUpdateSize(pmReadout *readout, int minCols, int minRows,
+                         int numCols, int numRows, bool mask)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    if (readout->image) {
+        *(psS32*) &(readout->col0) = PS_MIN(minCols, readout->col0);
+        *(psS32*) &(readout->row0) = PS_MIN(minRows, readout->row0);
+    } else {
+        *(psS32*) &(readout->col0) = minCols;
+        *(psS32*) &(readout->row0) = minRows;
+    }
+
+    // (reAllocate the images
+    if (!readout->image) {
+        readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    }
+    if (readout->image->numCols < numCols || readout->image->numRows < numRows) {
+        // Generate the new output image by extending the current one, or making a whole new one
+        psImage *newImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        psImageInit(newImage, 0.0);
+        psImageOverlaySection(newImage, readout->image, readout->col0, readout->row0, "=");
+        psFree(readout->image);
+        readout->image = newImage;
+    }
+
+    if (mask) {
+        if (!readout->mask) {
+            readout->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+        }
+        if (readout->mask->numCols < numCols || readout->mask->numRows < numRows) {
+            psImage *newMask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+            psImageInit(newMask, 0);
+            psImageOverlaySection(newMask, readout->mask, readout->col0, readout->row0, "=");
+            psFree(readout->mask);
+            readout->mask = newMask;
+        }
+    }
+
+    return true;
+}
+
+bool pmReadoutStackValidate(int *minInputColsPtr, int *maxInputColsPtr, int *minInputRowsPtr,
+                            int *maxInputRowsPtr, int *numColsPtr, int *numRowsPtr, 
+                            const psArray *inputs)
+{
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_PTR_NON_NULL(minInputColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(maxInputColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(minInputRowsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(maxInputRowsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(numColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(numRowsPtr, false);
+
+    // Step through each readout in the input image list to determine how big of an output image is needed to
+    // combine these input images.
+    int maxInputCols = 0;               // The largest input column value
+    int maxInputRows = 0;               // The largest input row value
+    int minInputCols = INT_MAX;         // The smallest input column value
+    int minInputRows = INT_MAX;         // The smallest input row value
+    int xSize = 0, ySize = 0;           // The size of the output image
+
+    int xMin = INT_MAX;
+    int yMin = INT_MAX;
+    int xMax = 0;
+    int yMax = 0;
+
+    bool valid = false;                 // Do we have a single valid input?
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+
+        if (!readout || !readout->image) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Input readout %ld is NULL or has NULL image.\n", i);
+            return false;
+        }
+
+        // use the trimsec to define the max full range of the output pixels
+        pmCell *cell = readout->parent; // The parent cell
+        bool mdok = true;       // Status of MD lookup
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psWarning("CELL.TRIMSEC is not set for readout %ld --- ignored.\n", i);
+        } else {
+            xSize = PS_MAX(xSize, trimsec->x1 - trimsec->x0);
+            ySize = PS_MAX(ySize, trimsec->y1 - trimsec->y0);
+            xMin  = PS_MIN(xMin,  trimsec->x0);
+            xMax  = PS_MAX(xMax,  trimsec->x1);
+            yMin  = PS_MIN(yMin,  trimsec->y0);
+            yMax  = PS_MAX(yMax,  trimsec->y1);
+        }
+
+        valid = true;
+
+        // Range of pixels on output images
+        minInputCols = PS_MAX(xMin, PS_MIN(minInputCols, readout->col0));
+        maxInputCols = PS_MIN(xMax, PS_MAX(maxInputCols, readout->col0 + readout->image->numCols));
+        minInputRows = PS_MAX(yMin, PS_MIN(minInputRows, readout->row0));
+        maxInputRows = PS_MIN(yMax, PS_MAX(maxInputRows, readout->row0 + readout->image->numRows));
+        psTrace("psModules.camera", 7, "Readout %ld: offset %d,%d; size %dx%d\n", i,
+                readout->col0, readout->row0, readout->image->numCols, readout->image->numRows);
+    }
+
+    if (minInputColsPtr) {
+        *minInputColsPtr = minInputCols;
+    }
+    if (maxInputColsPtr) {
+        *maxInputColsPtr = maxInputCols;
+    }
+    if (minInputRowsPtr) {
+        *minInputRowsPtr = minInputRows;
+    }
+    if (maxInputRowsPtr) {
+        *maxInputRowsPtr = maxInputRows;
+    }
+    if (numColsPtr) {
+        *numColsPtr = xSize;
+    }
+    if (numRowsPtr) {
+        *numRowsPtr = ySize;
+    }
+
+    return valid;
+}
Index: /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutStack.h
===================================================================
--- /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutStack.h	(revision 22290)
+++ /tags/pap_tags/pap_root_080117/psModules/src/camera/pmReadoutStack.h	(revision 22290)
@@ -0,0 +1,22 @@
+#ifndef PM_READOUT_STACK_H
+#define PM_READOUT_STACK_H
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+/// Update an output readout (for a stack) with the correct col0,row0 and the image size
+bool pmReadoutUpdateSize(pmReadout *readout, ///< Readout which to update
+                         int minCols, int minRows, ///< Minimum coordinates
+                         int numCols, int numRows, ///< Size of images
+                         bool mask      ///< Worry about the mask?
+    );
+
+/// Determine how large an output image is needed to combine the input readouts
+bool pmReadoutStackValidate(int *minInputColsPtr, int *maxInputColsPtr, ///< Min and max size in x
+                            int *minInputRowsPtr, int *maxInputRowsPtr, ///< Min and max size in y
+                            int *numColsPtr, int *numRowsPtr, ///< Size of image
+                            const psArray *inputs ///< Array of pmReadouts
+    );
+
+
+#endif
