Index: /branches/rel10_ifa/psModules/configure.ac
===================================================================
--- /branches/rel10_ifa/psModules/configure.ac	(revision 6447)
+++ /branches/rel10_ifa/psModules/configure.ac	(revision 6448)
@@ -1,5 +1,5 @@
 AC_PREREQ(2.59)
 
-AC_INIT([psmodule],[0.10.0],[http://pan-starrs.ifa.hawaii.edu/bugzilla])
+AC_INIT([psmodule],[0.9.99],[http://pan-starrs.ifa.hawaii.edu/bugzilla])
 AC_CONFIG_SRCDIR([psmodule.pc.in])
 
@@ -8,20 +8,8 @@
 AM_MAINTAINER_MODE
 
-dnl handle debug building
-AC_ARG_ENABLE(optimize,
-  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
-  [AC_MSG_RESULT(compile optimization enabled)
-   CFLAGS="${CFLAGS=} -O2"],
-  [AC_MSG_RESULT([compile optimization disabled])
-   CFLAGS="${CFLAGS=} -O0 -g"]
-)
-
-dnl handle profiler building
-AC_ARG_ENABLE(profile,
-  [AS_HELP_STRING(--enable-profile,enable compiler profiler information inclusion)],
-  [AC_MSG_RESULT(compile optimization enabled)
-   CFLAGS="${CFLAGS=} -fprofile-arcs -ftest-coverage -pg"]
-)
-
+dnl otherise AC_PROG_CC will default CFLAGS to "-g -02"
+if test -z ${CFLAGS} ; then
+  CFLAGS=""
+fi
 AC_SUBST([CFLAGS])
 
@@ -29,8 +17,7 @@
 AC_PROG_CC
 dnl FIXME document why GNU_SOURCE is being set
-dnl AC_GNU_SOURCE
+AC_GNU_SOURCE
 AC_PROG_INSTALL
 AM_PROG_LIBTOOL
-AC_SYS_LARGEFILE
 
 AC_PREFIX_DEFAULT([`pwd`])
@@ -62,12 +49,21 @@
 
 SRCPATH='${top_srcdir}/src'
-SRCDIRS="astrom camera config detrend imcombine imsubtract objects photom"
+SRCDIRS="astrom config detrend imcombine imsubtract objects"
 # escape two escapes at this level so \\ gets passed to the shell and \ to perl
 SRCINC=`echo "${SRCDIRS=}" | ${PERL} -pe "s|(\w+)|-I\\\\${SRCPATH=}/\1|g"`
 SRCINC="-I${SRCPATH=} ${SRCINC=}"
 SRCSUBLIBS=`echo "${SRCDIRS=}" | ${PERL} -pe "s|(\w+)|\1/libpsmodule\1.la|g"`
-AC_SUBST([SRCSUBLIBS],${SRCSUBLIBS=})
-AC_SUBST([SRCINC],${SRCINC=})
+AC_SUBST(SRCSUBLIBS,${SRCSUBLIBS=})
+AC_SUBST(SRCINC,${SRCINC=})
 AC_SUBST([SRCDIRS],${SRCDIRS=})
+
+dnl handle debug building
+AC_ARG_ENABLE(optimize,
+  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -O2"],
+  [AC_MSG_RESULT([compile optimization disabled])
+   CFLAGS="${CFLAGS=} -O0 -g"]
+)
 
 dnl doxygen -------------------------------------------------------------------
@@ -82,5 +78,5 @@
 
 if test -z ${PSLIB_CONFIG} ; then
-  PKG_CHECK_MODULES([PSLIB], [pslib >= 0.10.0])
+  PKG_CHECK_MODULES([PSLIB], [pslib >= 0.9.0])
 else
   AC_CHECK_FILE($PSLIB_CONFIG,[],
@@ -105,5 +101,4 @@
   src/Makefile
   src/astrom/Makefile
-  src/camera/Makefile
   src/config/Makefile
   src/detrend/Makefile
@@ -111,8 +106,6 @@
   src/imsubtract/Makefile
   src/objects/Makefile
-  src/photom/Makefile
   test/Makefile
   test/astrom/Makefile
-  test/camera/Makefile
   test/config/Makefile
   test/detrend/Makefile
@@ -120,5 +113,4 @@
   test/imsubtract/Makefile
   test/objects/Makefile
-  test/photom/Makefile
   Doxyfile
   psmodule-config
Index: /branches/rel10_ifa/psModules/src/astrom/Makefile.am
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/Makefile.am	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/astrom/Makefile.am	(revision 6448)
@@ -4,9 +4,31 @@
 libpsmoduleastrom_la_LDFLAGS  = -release $(PACKAGE_VERSION)
 libpsmoduleastrom_la_SOURCES  = \
-    pmAstrometry.c \
-    pmAstrometryObjects.c
+	pmFPA.c \
+	pmFPAAstrometry.c \
+	pmAstrometryObjects.c \
+	pmFPAConstruct.c \
+	pmFPARead.c \
+	pmFPAWrite.c \
+	pmReadout.c \
+	psAdditionals.c \
+	pmConcepts.c \
+	pmConceptsRead.c \
+	pmConceptsWrite.c \
+	pmConceptsStandard.c \
+	pmChipMosaic.c
 
 psmoduleincludedir = $(includedir)
 psmoduleinclude_HEADERS = \
-    pmAstrometry.h \
-    pmAstrometryObjects.h
+	pmFPA.h \
+	pmFPAAstrometry.h \
+	pmAstrometryObjects.h \
+	pmFPAConstruct.h \
+	pmFPARead.h \
+	pmFPAWrite.h \
+	pmReadout.h \
+	psAdditionals.h \
+	pmConcepts.h \
+	pmConceptsRead.h \
+	pmConceptsWrite.h \
+	pmConceptsStandard.h \
+	pmChipMosaic.h 
Index: /branches/rel10_ifa/psModules/src/astrom/pmAstrometry.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmAstrometry.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/astrom/pmAstrometry.c	(revision 6448)
@@ -10,9 +10,9 @@
 * XXX: We should review the extent of the warning messages on these functions
 * when the transformations are not successful.
-* 
+*
 * XXX: Should we implement non-linear cell->chip transforms?
-* 
-*  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
-*  @date $Date: 2006-01-26 21:10:50 $
+*
+*  @version $Revision: 1.12.4.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-02-17 17:13:41 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
@@ -25,5 +25,7 @@
 #include <math.h>
 #include "pslib.h"
+
 #include "pmAstrometry.h"
+#include "pmConcepts.h"
 
 /*****************************************************************************
@@ -60,21 +62,11 @@
         psFree(readout->mask);
         psFree(readout->weight);
-        //
-        // XXX: Not sure if this is the right way to do things.  Currently the psListAdd()
-        // increase the memory reference counter to the list data.  So, we
-        // iterate through the list, and decrement the reference counters.
-        //
-        if (1) {
-            if ((readout->bias != NULL) && (readout->bias->head != NULL)) {
-                psListElem *tmpElem = (psListElem *) readout->bias->head;
-                while (NULL != tmpElem) {
-                    psMemDecrRefCounter((psImage *) tmpElem->data);
-                    tmpElem = tmpElem->next;
-                }
-            }
-        }
-        psFree(readout->bias);
         psFree(readout->analysis);
+        #if 0
+
         psFree(readout->parent);
+        #endif
+
+        readout->parent = NULL;
     }
 }
@@ -88,8 +80,11 @@
         psFree(cell->concepts);
         psFree(cell->analysis);
+        psFree(cell->camera);
         //
         // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
         // in order to avoid memory reference counter problems.
         //
+        #if 0
+
         for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
             pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
@@ -99,7 +94,12 @@
             }
         }
+        psFree(cell->parent);
+        #endif
+
+        cell->parent = NULL;
+
         psFree(cell->readouts);
-        psFree(cell->parent);
         psFree(cell->hdu);
+
     }
 }
@@ -116,4 +116,6 @@
         // in order to avoid memory reference counter problems.
         //
+        #if 0
+
         for (psS32 i = 0 ; i < chip->cells->n ; i++) {
             pmCell *tmpCell = (pmCell *) chip->cells->data[i];
@@ -123,6 +125,9 @@
             }
         }
+        psFree(chip->parent);
+        #endif
+
+        chip->parent = NULL;
         psFree(chip->cells);
-        psFree(chip->parent);
         psFree(chip->hdu);
     }
@@ -143,4 +148,6 @@
         // in order to avoid memory reference counter problems.
         //
+        #if 0
+
         for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
             pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
@@ -150,4 +157,5 @@
             }
         }
+        #endif
         psFree(fpa->chips);
         psFree(fpa->hdu);
@@ -156,17 +164,28 @@
 }
 
+void p_pmHDUFree(p_pmHDU *hdu)
+{
+    if (hdu) {
+        psFree(hdu->extname);
+        psFree(hdu->header);
+        psFree(hdu->images);
+        psFree(hdu->masks);
+        psFree(hdu->weights);
+    }
+}
+
 // XXX: Verify these default values for row0, col0, rowBins, colBins
+// PAP: These values may disappear in the future in favour of values in parent->concepts?
 pmReadout *pmReadoutAlloc(pmCell *cell)
 {
     pmReadout *tmpReadout = (pmReadout *) psAlloc(sizeof(pmReadout));
 
-    tmpReadout->col0 = -1;
-    tmpReadout->row0 = -1;
-    tmpReadout->colBins = -1;
-    tmpReadout->rowBins = -1;
+    tmpReadout->col0 = 0;
+    tmpReadout->row0 = 0;
+    tmpReadout->colBins = 0;
+    tmpReadout->rowBins = 0;
     tmpReadout->image = NULL;
     tmpReadout->mask = NULL;
     tmpReadout->weight = NULL;
-    tmpReadout->bias = NULL;
     tmpReadout->analysis = psMetadataAlloc();
     tmpReadout->parent = cell;
@@ -179,13 +198,14 @@
 
 // XXX: Verify these default values for row0, col0.
+// PAP: These values may disappear in the future in favour of values in the "concepts"?
 pmCell *pmCellAlloc(
     pmChip *chip,
-    psMetadata *cameradata,
-    psString name)
+    psMetadata *cameraData,
+    const char *name)
 {
     pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
 
-    tmpCell->col0 = -1;
-    tmpCell->row0 = -1;
+    tmpCell->col0 = 0;
+    tmpCell->row0 = 0;
     tmpCell->toChip = NULL;
     tmpCell->toFPA = NULL;
@@ -196,6 +216,6 @@
         psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
     }
-    tmpCell->camera = cameradata;
-    tmpCell->analysis = NULL;
+    tmpCell->camera = psMemIncrRefCounter(cameraData);
+    tmpCell->analysis = psMetadataAlloc();
     tmpCell->readouts = psArrayAlloc(0);
     tmpCell->parent = chip;
@@ -203,6 +223,9 @@
         chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
     }
-    tmpCell->valid = false;
+    tmpCell->process = true;            // All cells are processed by default
+    tmpCell->exists = false;            // Not yet read in
     tmpCell->hdu = NULL;
+
+    pmConceptsBlankCell(tmpCell);
 
     psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
@@ -211,12 +234,13 @@
 
 // XXX: Verify these default values for row0, col0.
+// PAP: row0, col0 may disappear in the future in favour of storing values in the "concepts".
 pmChip *pmChipAlloc(
     pmFPA *fpa,
-    psString name)
+    const char *name)
 {
     pmChip *tmpChip = (pmChip *) psAlloc(sizeof(pmChip));
 
-    tmpChip->col0 = -1;
-    tmpChip->row0 = -1;
+    tmpChip->col0 = 0;
+    tmpChip->row0 = 0;
     tmpChip->toFPA = NULL;
     tmpChip->fromFPA = NULL;
@@ -226,5 +250,5 @@
         psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME to metadata.\n");
     }
-    tmpChip->analysis = NULL;
+    tmpChip->analysis = psMetadataAlloc();
     tmpChip->cells = psArrayAlloc(0);
     tmpChip->parent = fpa;
@@ -232,6 +256,9 @@
         fpa->chips = psArrayAdd(fpa->chips, 1, (psPtr) tmpChip);
     }
-    tmpChip->valid = false;
+    tmpChip->process = true;            // Work on all chips, by default
+    tmpChip->exists = false;            // Not read in yet
     tmpChip->hdu = NULL;
+
+    pmConceptsBlankChip(tmpChip);
 
     psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
@@ -248,11 +275,27 @@
     tmpFPA->concepts = psMetadataAlloc();
     tmpFPA->analysis = NULL;
-    tmpFPA->camera = camera;
+    tmpFPA->camera = psMemIncrRefCounter((psPtr)camera);
     tmpFPA->chips = psArrayAlloc(0);
     tmpFPA->hdu = NULL;
     tmpFPA->phu = NULL;
 
+    pmConceptsBlankFPA(tmpFPA);
+
     psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
     return(tmpFPA);
+}
+
+p_pmHDU *p_pmHDUAlloc(const char *extname)
+{
+    p_pmHDU *hdu = psAlloc(sizeof(p_pmHDU));
+    psMemSetDeallocator(hdu, (psFreeFunc)p_pmHDUFree);
+
+    hdu->extname = psStringCopy(extname);
+    hdu->header = NULL;
+    hdu->images = NULL;
+    hdu->masks = NULL;
+    hdu->weights = NULL;
+
+    return hdu;
 }
 
@@ -317,485 +360,26 @@
 
 
-/*****************************************************************************/
-/* 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);
+
+// 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;
 }
 
@@ -807,23 +391,26 @@
 {
     PS_ASSERT_PTR_NON_NULL(fpa, false);
-    if ((fpa->chips == NULL) || (chipNum >= fpa->chips->n)) {
+
+    psArray *chips = fpa->chips;        // Component chips
+    if ((chips == NULL) || (chipNum >= chips->n)) {
         return(false);
     }
-    psBool rc = true;
-
-    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
-        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
+
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) chips->data[i];
         if (tmpChip == NULL) {
-            rc = false;
+            continue;
+        }
+        if (i == chipNum) {
+            tmpChip->process = true;
+            setCellsProcess(tmpChip, true);
         } else {
-            if (i == chipNum) {
-                tmpChip->valid = true;
-            } else {
-                tmpChip->valid = false;
-            }
-        }
-    }
-
-    return(rc);
+            tmpChip->process = false;
+            setCellsProcess(tmpChip, false);
+        }
+
+    }
+
+    return true;
 }
 
@@ -831,15 +418,15 @@
 /*****************************************************************************
 XXX: The SDRS is ambiguous on a few things:
-    Whether or not the other chips should be set valid=true.
-    Should we return the number of chip valid=true before or after they're set,
+    Whether or not the other chips should be set process=true. [PAP: No]
+    Should we return the number of chip process=true before or after they're set, [PAP: After]
  *****************************************************************************/
 /**
- * 
- * pmFPAExcludeChip shall set valid to false only for the specified chip
+ *
+ * pmFPAExcludeChip shall set process to false only for the specified chip
  * number (chipNum). In the event that the specified chip number does not exist
  * within the fpa, the function shall generate a warning, and perform no action.
- * The function shall return the number of chips within the fpa that have valid
+ * The function shall return the number of chips within the fpa that have process
  * set to true.
- *  
+ *
  */
 int pmFPAExcludeChip(
@@ -849,21 +436,22 @@
     PS_ASSERT_PTR_NON_NULL(fpa, false);
 
-    if (fpa->chips == NULL) {
+    psArray *chips = fpa->chips;        // Component chips
+    if (chips == NULL) {
         psLogMsg(__func__, PS_LOG_WARN, "WARNING: fpa->chips == NULL\n");
         return(0);
     }
-    if ((chipNum >= fpa->chips->n) || (NULL == (pmChip *) fpa->chips->data[chipNum])) {
+    if ((chipNum >= chips->n) || (NULL == (pmChip *) chips->data[chipNum])) {
         psLogMsg(__func__, PS_LOG_WARN, "WARNING: the specified chip (%d) does not exist.\n", chipNum);
         return(0);
     }
 
-    psS32 numChips = 0;
-    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
-        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
+    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->valid = false;
-            } else {
-                tmpChip->valid = true;
+                tmpChip->process = false;
+                setCellsProcess(tmpChip, false); // Wipe out the cell as well
+            } else if (tmpChip->process) {
                 numChips++;
             }
@@ -874,2 +462,73 @@
 }
 
+
+bool pmCellSetWeights(pmCell *cell // Cell for which to set weights
+                     )
+{
+    float gain = psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN"); // Cell gain
+    float readnoise = psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); // Trim section
+
+    p_pmHDU *hdu = cell->hdu;           // The data unit, containing the weight and mask originals
+    if (!hdu) {
+        pmChip *chip = cell->parent;    // The parent chip
+        if (chip->hdu) {
+            hdu = chip->hdu;
+        } else {
+            pmFPA *fpa = chip->parent;  // The parent FPA
+            hdu = fpa->hdu;
+            if (!hdu) {
+                psError(PS_ERR_UNKNOWN, true, "Unable to find the HDU in the hierarchy!\n");
+                return false;
+            }
+        }
+    }
+
+    psArray *pixels = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    psArray *masks = hdu->masks;        // Array of mask images
+    // Generate the weights and masks if required
+    if (! weights) {
+        weights = psArrayAlloc(pixels->n);
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+            psImageInit(weights->data[i], 0.0);
+        }
+        hdu->weights = weights;
+    }
+    if (! masks) {
+        masks = psArrayAlloc(pixels->n);
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+            psImageInit(masks->data[i], 0);
+        }
+        hdu->masks = masks;
+    }
+
+    // Set the pixels
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout of interest
+
+        if (! readout->weight) {
+            readout->weight = weights->data[i];
+        }
+        if (! readout->mask) {
+            readout->mask = masks->data[i];
+        }
+
+        // Mask is already set to 0
+
+        // Set weight image to the variance = g*f + rn^2
+        psImage *image = psImageSubset(readout->image, *trimsec); // The pixels
+        psImage *weight = psImageSubset(readout->weight, *trimsec); // The weight map
+        psBinaryOp(weight, image, "*", psScalarAlloc(gain, PS_TYPE_F32));
+        psBinaryOp(weight, weight, "+", psScalarAlloc(readnoise*readnoise, PS_TYPE_F32));
+    }
+
+    return true;
+}
+
+
Index: /branches/rel10_ifa/psModules/src/astrom/pmAstrometry.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmAstrometry.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/astrom/pmAstrometry.h	(revision 6448)
@@ -8,6 +8,6 @@
 *  @author GLG, MHPCC
 *
-*  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
-*  @date $Date: 2006-01-26 21:10:50 $
+*  @version $Revision: 1.6.4.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-02-17 17:13:41 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
@@ -30,14 +30,13 @@
 {
     const char *extname;                // Extension name, if it corresponds to this level
-    psArray *pixels;                    // The pixel data, if it corresponds to this level
     psMetadata *header;                 // The FITS header, if it corresponds to this level
+    psArray *images;                    // The pixel data, if it corresponds to this level
+    psArray *masks;                     // The mask data, if it corresponds to this level
+    psArray *weights;                   // The weight data, if it corresponds to this level
 }
 p_pmHDU;
 
-p_pmHDU *pmHDUAlloc();
-void *pmHDUFree(p_pmHDU *hdu);
-
 /** Focal plane data structure
- * 
+ *
  *  A focal plane consists of one or more chips (according to the number of
  *  pieces of contiguous silicon). It contains metadata containers for the
@@ -52,5 +51,5 @@
  *  transformation which will be derived from numerically inverting the forward
  *  transformation.
- *  
+ *
  */
 typedef struct
@@ -71,5 +70,5 @@
 
 /** Chip data structure
- *  
+ *
  *  A chip consists of one or more cells (according to the number of amplifiers
  *  on the device). The chip contains metadata containers for the concepts and
@@ -82,5 +81,5 @@
  *  inverting the forward transformation. A boolean indicates whether the chip is
  *  of interest, allowing it to be excluded from analysis.
- *  
+ *
  */
 typedef struct
@@ -97,5 +96,6 @@
     psArray *cells;                     ///< The cells (referred to by name)
     pmFPA *parent;                      ///< Parent FPA
-    bool valid;                         ///< Do we bother about reading and working with this chip?
+    bool process;                       ///< Do we bother about reading and working with this chip?
+    bool exists;                        ///< Does the chip exist --- has it been read in?
     p_pmHDU *hdu;                       ///< FITS data
 }
@@ -126,5 +126,6 @@
     psArray *readouts;                  ///< The readouts (referred to by number)
     pmChip *parent;                     ///< Parent chip
-    bool valid;                         ///< Do we bother about reading and working with this cell?
+    bool process;                       ///< Do we bother about reading and working with this cell?
+    bool exists;                        ///< Does the cell exist --- has it been read in?
     p_pmHDU *hdu;                       ///< FITS data
 }
@@ -144,4 +145,5 @@
 {
     // Position on the cell
+    // XXX: These may be removed in the future; use parent->concepts instead?
     int col0;                           ///< Offset from the left of chip.
     int row0;                           ///< Offset from the bottom of chip.
@@ -152,5 +154,4 @@
     psImage *mask;                      ///< Mask of input image
     psImage *weight;                    ///< Weight of input image
-    psList *bias;                       ///< List of bias section (sub-)images
     psMetadata *analysis;               ///< Readout-level analysis metadata
     pmCell *parent;                     ///< Parent cell
@@ -183,11 +184,11 @@
  */
 pmCell *pmCellAlloc(
-    pmChip *chip,                        ///< Parent chip
-    psMetadata *cameradata,
-    psString name
+    pmChip *chip,       ///< Parent chip
+    psMetadata *cameraData, ///< Camera data
+    const char *name    ///< Name of cell
 );
 
 /** Allocates a pmChip
- * 
+ *
  *  The constructor shall make an empty pmChip. If the parent fpa is not NULL,
  *  the parent link is made and the chip shall be placed in the parent's array
@@ -199,16 +200,15 @@
  */
 pmChip *pmChipAlloc(
-    pmFPA *fpa,
-    psString name
-
+    pmFPA *fpa,                         ///< FPA to which the chip belongs
+    const char *name                    ///< Name of chip
 );
 
 /** Allocates a pmFPA
- * 
+ *
  *  The constructor shall make an empty pmFPA. The chips array shall be
  *  allocated with a zero size, the camera and db pointers set to the values
  *  provided, and the concepts metadata constructed. All other pointers in the
  *  structure shall be initialized to NULL.
- * 
+ *
  */
 pmFPA *pmFPAAlloc(
@@ -216,12 +216,21 @@
 );
 
+/** Allocates a p_pmHDU
+ *
+ * XXX: More detailed description
+ *
+ * @return p_pmHDU*    newly allocated p_pmHDU
+ */
+p_pmHDU *p_pmHDUAlloc(const char *extname // Extension name
+                     );
+
 
 /** Verify parent links.
- * 
+ *
  *  This function checks the validity of the parent links in the FPA hierarchy.
  *  If a parent link is not set (or not set correctly), it is corrected, and the
  *  function shall return false. If all the parent pointers were correct, the
  *  function shall return true.
- * 
+ *
  */
 bool pmFPACheckParents(
@@ -232,10 +241,38 @@
 
 /** FUNC DESC
- * 
- * 
- * 
- * 
- */
-
+ *
+ *
+ *
+ *
+ */
+
+
+
+/**
+ *
+ * pmFPASelectChip shall set valid to true for the specified chip number
+ * (chipNum), and all other chips shall have valid set to false. In the event
+ * that the specified chip number does not exist within the fpa, the function
+ * shall return false.
+ *
+ */
+bool pmFPASelectChip(
+    pmFPA *fpa,
+    int chipNum
+);
+
+/**
+ *
+ * pmFPAExcludeChip shall set valid to false only for the specified chip
+ * number (chipNum). In the event that the specified chip number does not exist
+ * within the fpa, the function shall generate a warning, and perform no action.
+ * The function shall return the number of chips within the fpa that have valid
+ * set to true.
+ *
+ */
+int pmFPAExcludeChip(
+    pmFPA *fpa,
+    int chipNum
+);
 
 
@@ -426,47 +463,4 @@
 );
 
-/**
- * 
- * pmFPASelectChip shall set valid to true for the specified chip number
- * (chipNum), and all other chips shall have valid set to false. In the event
- * that the specified chip number does not exist within the fpa, the function
- * shall return false.
- *  
- */
-bool pmFPASelectChip(
-    pmFPA *fpa,
-    int chipNum
-);
-
-
-/**
- * 
- * pmFPAExcludeChip shall set valid to false only for the specified chip
- * number (chipNum). In the event that the specified chip number does not exist
- * within the fpa, the function shall generate a warning, and perform no action.
- * The function shall return the number of chips within the fpa that have valid
- * set to true.
- *  
- */
-int pmFPAExcludeChip(
-    pmFPA *fpa,
-    int chipNum
-);
-
-
-/**
- * 
- * pmFPAConstruct shall construct a focal plane hierarchy from a camera
- * configuration. A db handle is also provided so that may be set in the pmFPA.
- * The resultant pmFPA and its lower-down components shall be ready for to read a
- * FITS file into it by setting the extname pointers at the appropriate levels to
- * the appropriate FITS extension name.
- *  
- */
-pmFPA *pmFPAConstruct(
-    const psMetadata *camera,
-    psDB *db
-);
-
 
 #endif // #ifndef PS_ASTROMETRY_H
Index: /branches/rel10_ifa/psModules/src/astrom/pmAstrometryObjects.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmAstrometryObjects.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/astrom/pmAstrometryObjects.c	(revision 6448)
@@ -8,6 +8,6 @@
 *  @author EAM, IfA
 *
-*  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
-*  @date $Date: 2005-12-05 20:49:30 $
+*  @version $Revision: 1.1.14.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-02-17 17:13:41 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
@@ -20,5 +20,5 @@
 #include <math.h>
 #include "pslib.h"
-#include "pmAstrometry.h"
+#include "pmFPA.h"
 #include "pmAstrometryObjects.h"
 
@@ -536,5 +536,6 @@
     psArray *rot;
 
-    pmAstromStats minStat, newStat;
+    pmAstromStats minStat;
+    pmAstromStats newStat = {{0, 0, 0, 0}, {0, 0, 0, 0}, 0, 0, 0, 0};
     psPlane center;
 
Index: /branches/rel10_ifa/psModules/src/astrom/pmAstrometryObjects.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmAstrometryObjects.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/astrom/pmAstrometryObjects.h	(revision 6448)
@@ -8,6 +8,6 @@
 *  @author EAM, IfA
 *
-*  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
-*  @date $Date: 2006-01-26 21:10:50 $
+*  @version $Revision: 1.2.4.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-02-17 17:13:41 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
@@ -21,8 +21,8 @@
 # include <unistd.h>   // for unlink
 # include <pslib.h>
-# include <pmAstrometry.h>
-
-/*
- * 
+# include <pmFPA.h>
+
+/*
+ *
  * This structure specifies the coordinate of the detection in each of the
  * four necessary coordinate frames: pix defines the position in the psReadout
@@ -35,7 +35,7 @@
  * coordinates, while the reference detections will be projected to the other
  * frames from the sky coordinates.
- * 
+ *
  * XXX: There are more members here than in the SDRS.
- * 
+ *
  */
 typedef struct
@@ -55,9 +55,9 @@
 
 /*
- * 
+ *
  * The pmAstromMatch structure defines the cross-correlation between two
  * arrays. A single such data item specifies that item number pmAstromMatch.idx1
  * in the first list corresponds to pmAstromMatch.idx2 in the second list.
- * 
+ *
  */
 typedef struct
@@ -70,7 +70,7 @@
 
 /*
- * 
+ *
  * XXX: Not in SDRS.
- * 
+ *
  */
 typedef struct
@@ -88,5 +88,5 @@
 
 /*
- * 
+ *
  * If the two sets of coordinates are expected to agree very well (ie, the
  * current best-guess astrometric solution is quite close to the radius. The
@@ -97,5 +97,5 @@
  * ASTROM.MATCH.RADIUS). The output consists an array of pmAstromMatch values,
  * defined below.
- * 
+ *
  */
 psArray *pmAstromRadiusMatch(
@@ -108,11 +108,11 @@
 
 /*
- * 
+ *
  * This function accepts an array of pmAstromObj objects and rotates them by
  * the given angle about the given center coordinate pCenter,qCenter in the Focal
  * Plane Array coordinates.
- * 
+ *
  * XXX: This differs from the SDRS
- * 
+ *
  */
 /* SDRS
@@ -132,5 +132,5 @@
 
 /*
- * 
+ *
  * If the two sets of coordinates are not known to agree well, but the
  * relative scale and approximate relative rotation is known, then a much faster
@@ -147,7 +147,7 @@
  * allowing the procedure to scan over a range of rotations. We define the
  * following function to apply this matching algorithm:
- * 
+ *
  * XXX: In the SDRS, this function is a pointer.
- * 
+ *
  */
 pmAstromStats pmAstromGridMatch(
@@ -159,5 +159,5 @@
 
 /*
- * 
+ *
  * The result of a pmAstromGridMatch may be used to modify the astrometry
  * transformation information for a pmFPA image hierarchy structure. The result
@@ -167,7 +167,7 @@
  * the linear terms of the pmFPA.toTangentPlane transformation. These two
  * adjustments are made using the function:
- * 
+ *
  * XXX: This function name is different in the SDRS.
- * 
+ *
  */
 psPlaneTransform *pmAstromGridApply(
@@ -178,9 +178,11 @@
 
 /*
- * 
+ *
  * This function is identical to pmAstromGridMatch, but is valid for only a
  * single relative rotation. The input config information need not contain any of
  * the GRID.*.ANGLE entries (they will be ignored).
- * 
+ *
+ * XXX: This function name is different in the SDRS.
+ *
  */
 /* in pmAstromGrid.c */
@@ -193,5 +195,5 @@
 
 /*
- * 
+ *
  * This function accepts the raw and reference source lists and the list of
  * matched entries. It uses the matched list to determine a polynomial
@@ -207,7 +209,7 @@
  * modifications to pmFPA.toTangentPlane incorporate the rotation component of
  * the linear terms and the higher-order terms of the polynomial fits.
- * 
+ *
  * XXX: No prototype code.
- * 
+ *
  */
 bool pmAstromFitFPA(
@@ -233,7 +235,7 @@
  *ASTROM.ORDER).  The result of this fit is a set of modifications of the
  *components of the pmChip.toFPA transformation.
- * 
+ *
  * XXX: No prototype code.
- * 
+ *
  */
 bool pmAstromFitChip(
@@ -247,5 +249,5 @@
 
 /*
- * 
+ *
  * The following function determines the position residual, in the tangent
  * plane, as a function of position in the focal plane, for a collection of raw
@@ -253,7 +255,7 @@
  * the bin size over which the gradient is measured (keyword: ASTROM.GRAD.BOX).
  * The function returns an array of pmAstromGradient structures, defined below.
- * 
+ *
  * XXX: No prototype code.
- * 
+ *
  */
 psArray pmAstromMeasureGradients(
@@ -267,9 +269,9 @@
 
 /*
- * 
+ *
  * The following data structure carries the information about the residual
  * gradient of source positions in the tangent plane (pmAstromObj.TP) as a
  * function of position in the focal plane (pmAstromObj.FP).
- * 
+ *
  */
 typedef struct
@@ -283,5 +285,5 @@
 
 /*
- * 
+ *
  * The gradient set measured above can be fitted with a pair of 2D
  * polynomials. The resulting fits can then be related back to the implied
@@ -290,7 +292,7 @@
  * supplied pmFPA structure. The configuration variable supplies the polynomial
  * order (keyword: ASTROM.DISTORT.ORDER).
- * 
+ *
  * XXX: No prototype code.
- * 
+ *
  */
 psArray pmAstromFitDistortion(
@@ -308,15 +310,15 @@
  ******************************************************************************/
 /*
- * 
- * 
- * 
- * 
- */
-
-
-/*
- * 
+ *
+ *
+ *
+ *
+ */
+
+
+/*
+ *
  * Allocates a pmAstromObj struct.
- * 
+ *
  */
 pmAstromObj *pmAstromObjAlloc (void);
@@ -325,7 +327,7 @@
 
 /*
- * 
+ *
  * Copies a pmAstromObj struct.
- * 
+ *
  */
 pmAstromObj *pmAstromObjCopy(
@@ -336,7 +338,7 @@
 
 /*
- * 
- * 
- * 
+ *
+ *
+ *
  */
 pmAstromMatch *pmAstromMatchAlloc(
@@ -349,7 +351,7 @@
 
 /*
- * 
- * 
- * 
+ *
+ *
+ *
  */
 psPlaneTransform *pmAstromMatchFit(
@@ -364,7 +366,7 @@
 
 /*
- * 
- * 
- * 
+ *
+ *
+ *
  */
 int pmAstromObjSortByFPX(
@@ -376,7 +378,7 @@
 
 /*
- * 
- * 
- * 
+ *
+ *
+ *
  */
 int pmAstromObjSortByMag(
Index: /branches/rel10_ifa/psModules/src/astrom/pmChipMosaic.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmChipMosaic.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmChipMosaic.c	(revision 6448)
@@ -0,0 +1,367 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmChipMosaic.h"
+
+#define MEM_LEAKS 0
+
+// Compare a value with a maximum and minimum
+#define COMPARE(value,min,max) \
+if ((value) < (min)) { \
+    (min) = (value); \
+} \
+if ((value) > (max)) { \
+    (max) = (value); \
+}
+
+// Mosaic multiple images, with flips, binning and offsets
+psImage *p_pmImageMosaic(const psArray *source, // Images to splice in
+                         const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                         const psVector *xBinSource, const psVector *yBinSource, // Binning in x and 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
+                        )
+{
+    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);
+
+    // 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;
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+
+        // 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(__func__, 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);
+    }
+
+    // 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(__func__, 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, 0.0);
+
+    // Next pass through the images to do the mosaicking
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+        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; useful to test psImageOverlaySection if no other reason
+            psImageOverlaySection(mosaic, image, x0->data.S32[i], y0->data.S32[i], "+");
+        } else {
+            // We have to do the hard work ourself
+            for (int y = 0; y < image->numRows; y++) {
+                int yParity = yFlip->data.U8[i] ? -1 : 1;
+                float yTargetBase = (y0->data.S32[i] + yParity * yBinSource->data.S32[i] * y) / yBinTarget;
+                for (int x = 0; x < image->numCols; x++) {
+                    int xParity = xFlip->data.U8[i] ? -1 : 1;
+                    float xTargetBase = (x0->data.S32[i] + xParity * xBinSource->data.S32[i] * x) /
+                                        xBinTarget;
+
+                    // In case the original image is binned but the mosaic is not, we need to fill in the
+                    // values in the mosaic.
+                    #define FILL_IN(TYPE)                                                                    \
+                    for (int j = 0; j < yBinSource->data.S32[i]; j++) {                                      \
+                        int yTarget = (int)(yTargetBase + yParity * (float)j / (float)yBinTarget);           \
+                        for (int i = 0; i < xBinSource->data.S32[i]; i++) {                                  \
+                            int xTarget = (int)(xTargetBase + xParity * (float)i / (float)xBinTarget);       \
+                            mosaic->data.TYPE[yTarget][xTarget] += image->data.TYPE[y][x];                   \
+                        }                                                                                    \
+                    }
+
+                    switch (type) {
+                    case PS_TYPE_F32:
+                        FILL_IN(F32);
+                        break;
+                    case PS_TYPE_U8:
+                        FILL_IN(U8);
+                        break;
+                    default:
+                        psAbort(__func__, "Should never get here.\n");
+                    }
+
+                }
+            } // Iterating over input image
+        }
+    }
+
+    return mosaic;
+}
+
+
+// Set the concepts in the new cell, based on the values in the old one
+static bool cellConcepts(pmCell *target,// Target cell
+                         psArray *sources, // Source cells
+                         int xBin, int yBin // Binning
+                        )
+{
+    bool success = true;                // Result of setting everything
+    float gain       = 0.0;             // Gain
+    float readnoise  = 0.0;             // Read noise
+    float saturation = 0.0;             // Saturation level
+    float bad        = 0.0;             // Bad level
+    float exposure   = 0.0;             // Exposure time
+    float darktime   = 0.0;             // Dark time
+    double time      = 0.0;             // Time of observation
+    psTimeType timeSys = 0;             // Time system
+
+    int nCells = 0;                     // Number of cells;
+    for (int i = 0; i < sources->n; i++) {
+        pmCell *cell = sources->data[i];// The cell of interest
+        if (!cell) {
+            continue;
+        }
+
+        nCells++;
+        gain       += psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN");
+        readnoise  += psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE");
+        saturation += psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION");
+        bad        += psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD");
+        exposure   += psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE");
+        darktime   += psMetadataLookupF32(NULL, cell->concepts, "CELL.DARKTIME");
+        time       += psTimeToMJD(psMetadataLookupPtr(NULL, cell->concepts, "CELL.TIME"));
+        if (i == 0) {
+            timeSys = psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS");
+        } else if (timeSys != psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS")) {
+            psLogMsg(__func__, PS_LOG_ERROR, "Differing time systems in use: %d vs %d\n", timeSys,
+                     psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS"));
+            success = false;
+        }
+    }
+    gain       /= (float)nCells;
+    readnoise  /= (float)nCells;
+    saturation /= (float)nCells;
+    bad        /= (float)nCells;
+    exposure   /= (float)nCells;
+    darktime   /= (float)nCells;
+    psTime *timePtr = psTimeFromMJD(time/(double)nCells);
+    timePtr = psTimeConvert(timePtr, timeSys);
+
+    // XXX *REALLY* need a generic "concept update" function that handles the type and comments transparently.
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.GAIN", PS_META_REPLACE, "Gain (e/ADU)", gain);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.READNOISE", PS_META_REPLACE, "Read noise (e)", readnoise);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.SATURATION", PS_META_REPLACE, "Saturation level (ADU)", saturation);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.BAD", PS_META_REPLACE, "Bad level (ADU)", bad);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "Exposure time (sec)", exposure);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.DARKTIME", PS_META_REPLACE, "Time since last CCD flush (sec)", darktime);
+    psMetadataAddPtr(target->concepts, PS_LIST_TAIL, "CELL.TIME", PS_DATA_TIME | PS_META_REPLACE, "Time of observation", timePtr);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_META_REPLACE, "Time system", timeSys);
+    psFree(timePtr);
+
+    // Now fill in the ones I know by other means
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.X0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.Y0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", 1);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", 1);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_META_REPLACE, "Binning in x", xBin);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_META_REPLACE, "Binning in x", yBin);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.READDIR", PS_META_REPLACE, "Read direction (faked)", 1);
+    psRegion *trimsec = psMetadataLookupPtr(NULL, target->concepts, "CELL.TRIMSEC");
+    trimsec->x0 = trimsec->x1 = trimsec->y0 = trimsec->y1 = 0.0;
+
+    return success;
+}
+
+
+
+// Mosaic a chip together into a single image
+int pmChipMosaic(pmChip *chip,// Chip to mosaic
+                 int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                )
+{
+
+    psArray *cells = chip->cells;       // The array of cells
+    psArray *images = psArrayAlloc(cells->n); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(cells->n); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(cells->n); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(cells->n, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(cells->n, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(cells->n, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(cells->n, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(cells->n, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(cells->n, PS_TYPE_U8); // Flip in y?
+
+    // Set up the required inputs
+    psTrace(__func__, 1, "Mosaicking %d cells...\n", cells->n);
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        if (!cell) {
+            continue;
+        }
+        x0->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+        y0->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+        psTrace(__func__, 5, "Cell %d: x0=%d y0=%d\n", i, x0->data.S32[i], y0->data.S32[i]);
+        xBin->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN");
+        yBin->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN");
+        int xParity = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+        int yParity = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+        if (xParity == 1) {
+            xFlip->data.U8[i] = 0;
+        } else if (xParity == -1) {
+            xFlip->data.U8[i] = 1;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "The x parity of cell %d is not +/- 1 (it's %d) --- "
+                     "assuming +1.\n", i, xParity);
+            xFlip->data.U8[i] = 0;
+        }
+        if (yParity == 1) {
+            yFlip->data.U8[i] = 0;
+        } else if (yParity == -1) {
+            yFlip->data.U8[i] = 1;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "The y parity of cell %d is not +/- 1 (it's %d) --- "
+                     "assuming +1.\n", i, yParity);
+            yFlip->data.U8[i] = 0;
+        }
+
+        psArray *readouts = cell->readouts; // The array of readouts
+        if (readouts->n > 1) {
+            psLogMsg(__func__, PS_LOG_WARN, "Cell %d contains more than one readout --- only the first will "
+                     "be mosaicked.\n", i);
+        }
+        pmReadout *readout = readouts->data[0]; // The only readout we'll bother with
+
+        // The images to put into the mosaic
+        images->data[i]  = psMemIncrRefCounter(readout->image);
+        weights->data[i] = psMemIncrRefCounter(readout->weight);
+        masks->data[i]   = psMemIncrRefCounter(readout->mask);
+    }
+    // Mosaic the images together and we're done
+    psImage *image = p_pmImageMosaic(images, xFlip, yFlip, xBin, yBin, xBinChip, yBinChip, x0, y0);
+    psImage *weight = p_pmImageMosaic(weights, xFlip, yFlip, xBin, yBin, xBinChip, yBinChip, x0, y0);
+    psImage *mask = p_pmImageMosaic(masks, xFlip, yFlip, xBin, yBin, xBinChip, yBinChip, x0, y0);
+
+    // Clean up
+    psFree(x0);
+    psFree(y0);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    int nCells = cells->n;
+
+    // Fix up the HDU
+    if (chip->parent->hdu) {
+        psLogMsg(__func__, PS_LOG_WARN, "The original format has the entire FPA in a single extension.  "
+                 "The FPA hierarchy may be invalid following the pmChipMosaic.\n");
+    } else {
+        if (! chip->hdu) {
+            psString chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+            chip->hdu = p_pmHDUAlloc(chipName);
+        }
+        p_pmHDU *hdu = chip->hdu;
+        psArrayElementsFree(hdu->images);
+        psArrayElementsFree(hdu->weights);
+        psArrayElementsFree(hdu->masks);
+        hdu->images  = psArrayRealloc(hdu->images,1);
+        hdu->weights = psArrayRealloc(hdu->weights, 1);
+        hdu->masks   = psArrayRealloc(hdu->masks, 1);
+        hdu->images->data[0]  = image;
+        hdu->weights->data[0] = weight;
+        hdu->masks->data[0]   = mask;
+        psMetadataAddS32(hdu->header, PS_LIST_TAIL, "NAXIS1", PS_META_REPLACE, "Number of columns", image->numCols);
+        psMetadataAddS32(hdu->header, PS_LIST_TAIL, "NAXIS2", PS_META_REPLACE, "Number of rows", image->numRows);
+    }
+
+    // Chop off all the component cells, and construct a new one
+    pmCell *newCell = pmCellAlloc(NULL, NULL, __func__); // New cell
+    cellConcepts(newCell, cells, xBinChip, yBinChip);
+    pmChipFreeCells(chip);
+    // Have to put in the new cell manually, since we didn't want to put it in before blowing the cells away.
+    newCell->parent = chip;
+    psArrayAdd(chip->cells, 1, newCell);
+    newCell->exists = true;
+    newCell->process = true;
+
+    // Now make a new readout to go in the new cell
+    pmReadout *newReadout = pmReadoutAlloc(newCell); // New readout
+    // Want the readouts to contain a subimage, but that subimage is the whole image.
+    // This preserves the relationship there was before, where freeing the parent frees the child.
+    psRegion entire = {0.0, 0.0, 0.0, 0.0};
+    newReadout->image = psMemIncrRefCounter(psImageSubset(image, entire));
+    newReadout->weight = psMemIncrRefCounter(psImageSubset(weight, entire));
+    newReadout->mask = psMemIncrRefCounter(psImageSubset(mask, entire));
+    // Drop references
+    psFree(newReadout);
+    psFree(newCell);
+
+    // Well, we've stuffed around with the camera configuration, so it's no longer valid...
+    #if 0
+
+    psFree(chip->parent->camera);
+    chip->parent->camera = NULL;
+    #endif
+
+    return nCells;
+}
+
+
+int pmFPAMosaicCells(pmFPA *fpa,        // FPA
+                     int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                    )
+{
+    assert(fpa);
+
+    int numChips = 0;
+    psArray *chips = fpa->chips;        // Component chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The chip of interest
+        if (! chip || ! chip->exists || ! chip->process) {
+            continue;
+        }
+
+        numChips++;
+        pmChipMosaic(chip, xBinChip, yBinChip); // Mosaic of cells within the chip
+
+    }
+
+    return numChips;
+
+}
Index: /branches/rel10_ifa/psModules/src/astrom/pmChipMosaic.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmChipMosaic.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmChipMosaic.h	(revision 6448)
@@ -0,0 +1,26 @@
+#ifndef PM_CHIP_MOSAIC_H
+#define PM_CHIP_MOSAIC_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Mosaic multiple images, with flips, binning and offsets
+psImage *p_pmImageMosaic(const psArray *source, // Images to splice in
+                         const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                         const psVector *xBinSource, const psVector *yBinSource, // Binning in x and 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
+                        );
+
+// Mosaic a chip together into a single cell with single readout
+int pmChipMosaic(pmChip *chip,// Chip to mosaic
+                 int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                );
+
+// Mosaic all the cells in all (valid) chips together (neglecting the overscans); return number of chips
+int pmFPAMosaicCells(pmFPA *fpa,        // FPA
+                     int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                    );
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmConcepts.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConcepts.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConcepts.c	(revision 6448)
@@ -0,0 +1,576 @@
+// XXX *REALLY* need generic "concept update" and "concept read" functions that handles the type transparently
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "pslib.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmConceptsStandard.h"
+#include "psAdditionals.h"
+
+static bool conceptsInitialised = false;// Have concepts been read?
+static psMetadata *conceptsFPA = NULL;  // Known concepts for FPA
+static psMetadata *conceptsChip = NULL; // Known concepts for chip
+static psMetadata *conceptsCell = NULL; // Known concepts for cell
+
+// Free a concept
+static void conceptSpecFree(pmConceptSpec *spec)
+{
+    psFree(spec->blank);
+}
+
+pmConceptSpec *pmConceptSpecAlloc(psMetadataItem *blank, // Blank value; contains the name
+                                  pmConceptReadFunc read, // Function to call to read the concept
+                                  pmConceptWriteFunc write // Function to call to write the concept
+                                 )
+{
+    pmConceptSpec *spec = psAlloc(sizeof(pmConceptSpec));
+    psMemSetDeallocator(spec, (psFreeFunc)conceptSpecFree);
+
+    spec->blank = psMemIncrRefCounter(blank);
+    spec->read = read;
+    spec->write = write;
+
+    return spec;
+}
+
+
+bool pmConceptRegister(psMetadataItem *blank, // Blank value; contains the name
+                       pmConceptReadFunc read, // Function to call to read the concept
+                       pmConceptWriteFunc write, // Function to call to write the concept
+                       pmConceptLevel level // Level at which to store concept in the FPA hierarchy
+                      )
+{
+    assert(blank);
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    pmConceptSpec *spec = pmConceptSpecAlloc(blank, read, write); // The concept specification
+    psMetadata **target = NULL;         // The metadata of known concepts to write to
+    switch (level) {
+    case PM_CONCEPT_LEVEL_FPA:
+        target = &conceptsFPA;
+        break;
+    case PM_CONCEPT_LEVEL_CHIP:
+        target = &conceptsChip;
+        break;
+    case PM_CONCEPT_LEVEL_CELL:
+        target = &conceptsCell;
+        break;
+    default:
+        psError(PS_ERR_IO, true, "Unknown concept level provided: %d\n", level);
+        psFree(spec);
+        return false;
+    }
+
+    psMetadataAdd(*target, PS_LIST_TAIL, blank->name, PS_DATA_UNKNOWN | PS_META_REPLACE,
+                  "Concepts specification", spec);
+    psFree(spec);                       // Drop reference
+
+    return true;
+}
+
+// This function gets called for the really boring concepts --- where all you have to do is read from a header
+// or database and you don't need to muck around with conversions.
+// There is no similar "writePlain", since the type is already known.
+static psMetadataItem *readPlain(psMetadataItem *blank, // The blank value with name, comment, type
+                                 pmFPA *fpa, // The FPA of interest
+                                 pmChip *chip, // The Chip of interest
+                                 pmCell *cell, // The cell of interest
+                                 psDB *db // Database handle
+                                )
+{
+    switch (blank->type) {
+    case PS_DATA_STRING: {
+            psString string = pmConceptReadString(fpa, chip, cell, db, blank->name);
+            psMetadataItem *item = psMetadataItemAllocStr(blank->name, blank->comment, string);
+            psFree(string);
+            return item;
+        }
+    case PS_DATA_S32:
+        return psMetadataItemAllocS32(blank->name, blank->comment,
+                                      pmConceptReadS32(fpa, chip, cell, db, blank->name));
+    case PS_DATA_F32:
+        return psMetadataItemAllocF32(blank->name, blank->comment,
+                                      pmConceptReadF32(fpa, chip, cell, db, blank->name));
+    case PS_DATA_F64:
+        return psMetadataItemAllocF64(blank->name, blank->comment,
+                                      pmConceptReadF64(fpa, chip, cell, db, blank->name));
+    default:
+        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) is not of a standard type (%x)\n",
+                 blank->name, blank->comment, blank->type);
+    }
+    return NULL;
+}
+
+// Set all registered concepts to blank value for the specified level
+static bool conceptsBlank(psMetadata **specs,  // One of the concepts specifications
+                          psMetadata *target // Place to install the concepts
+                         )
+{
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(*specs, PS_LIST_HEAD, NULL); // Iterator on specs
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        psTrace(__func__, 9, "Blanking %s...\n", specItem->name);
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psMetadataItem *blank = spec->blank; // The concept
+        psMetadataAddItem(target, blank, PS_LIST_TAIL, PS_META_REPLACE);
+    }
+    psFree(specsIter);
+
+    return true;
+}
+
+// Read all registered concepts for the specified level
+static bool conceptsRead(psMetadata **specs, // One of the concepts specifications
+                         pmFPA *fpa,    // The FPA
+                         pmChip *chip,  // The chip
+                         pmCell *cell,  // The cell
+                         psDB *db,      // Database handle
+                         psMetadata *target // Place into which to read the concepts
+                        )
+{
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(*specs, PS_LIST_HEAD, NULL); // Iterator on specs
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psMetadataItem *conceptItem = NULL; // The item to add to the concepts
+        if (spec->read) {
+            conceptItem = spec->read(fpa, chip, cell, db);
+        } else {
+            conceptItem = readPlain(spec->blank, fpa, chip, cell, db);
+            if (conceptItem->type != spec->blank->type) {
+                psLogMsg(__func__, PS_LOG_ERROR, "Type of concept %s following read (%x) does not match "
+                         "blank type (%x).\n", spec->blank->name, conceptItem->type, spec->blank->type);
+            }
+        }
+        psMetadataAddItem(target, conceptItem, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(conceptItem);            // Drop reference
+    }
+    psFree(specsIter);
+    return true;
+}
+
+// Write all registered concepts for the specified level
+static bool conceptsWrite(psMetadata **specs, // One of the concepts specifications
+                          pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,      // Database handle
+                          psMetadata *source // The concepts to write out
+                         )
+{
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+    if (! fpa->camera) {
+        return false;
+    }
+    psMetadataIterator *iter = psMetadataIteratorAlloc(source, PS_LIST_HEAD, NULL); // Iterator on concepts
+    psMetadataItem *item = NULL;    // Item from the concepts
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        const char *name = item->name;  // Name of the concept
+        if (!strcmp(name, "CELL.NAME") || !strcmp(name, "CHIP.NAME")) {
+            // These concepts are not written out; they are set from things like the FITS extname
+            continue;
+        }
+        psMetadataItem *specItem = psMetadataLookup(*specs, name); // Specification for the concept
+        if (specItem) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            if (spec->write) {
+                spec->write(fpa, chip, cell, db); // The concept
+            } else {
+                pmConceptWrite(fpa, chip, cell, db, source, spec->blank->name);
+            }
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to find specification to write concept %s in FPA\n",
+                     name);
+        }
+    }
+    psFree(iter);
+
+    return true;
+}
+
+// Set the concepts for a given FPA to blanks
+bool pmConceptsBlankFPA(pmFPA *fpa    // FPA for which to set blank concepts
+                       )
+{
+    psTrace("psModule.concepts", 5, "Blanking FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
+    return conceptsBlank(&conceptsFPA, fpa->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsReadFPA(pmFPA *fpa,      // FPA for which to read concepts
+                       psDB *db         // Database handle
+                      )
+{
+    psTrace("psModule.concepts", 5, "Reading FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
+    return conceptsRead(&conceptsFPA, fpa, NULL, NULL, db, fpa->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsWriteFPA(pmFPA *fpa,     // FPA for which to write concepts
+                        psDB *db        // Database handle
+                       )
+{
+    psTrace("psModule.concepts", 5, "Writing FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
+    return conceptsWrite(&conceptsFPA, fpa, NULL, NULL, db, fpa->concepts);
+}
+
+// Set the concepts for a given chip to blanks
+bool pmConceptsBlankChip(pmChip *chip // FPA for which to set blank concepts
+                        )
+{
+    psTrace("psModule.concepts", 5, "Blanking chip concepts: %x %x\n", conceptsChip, chip->concepts);
+    return conceptsBlank(&conceptsChip, chip->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsReadChip(pmChip *chip,   // Chip for which to read concepts
+                        psDB *db        // Database handle
+                       )
+{
+    psTrace("psModule.concepts", 5, "Reading chip concepts: %x %x\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsRead(&conceptsChip, fpa, chip, NULL, db, chip->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsWriteChip(pmChip *chip,  // Chip for which to write concepts
+                         psDB *db        // Database handle
+                        )
+{
+    psTrace("psModule.concepts", 5, "Writing chip concepts: %x %x\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsWrite(&conceptsChip, fpa, chip, NULL, db, chip->concepts);
+}
+
+// Set the concepts for a given chip to blanks
+bool pmConceptsBlankCell(pmCell *cell // Cell for which to set blank concepts
+                        )
+{
+    psTrace("psModule.concepts", 5, "Blanking cell concepts: %x %x\n", conceptsCell, cell->concepts);
+    return conceptsBlank(&conceptsCell, cell->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsReadCell(pmCell *cell,   // Cell for which to read concepts
+                        psDB *db        // Database handle
+                       )
+{
+    psTrace("psModule.concepts", 5, "Writing cell concepts: %x %x\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsRead(&conceptsCell, fpa, chip, cell, db, cell->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsWriteCell(pmCell *cell,  // FPA for which to write concepts
+                         psDB *db       // Database handle
+                        )
+{
+    psTrace("psModule.concepts", 5, "Writing cell concepts: %x %x\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsWrite(&conceptsCell, fpa, chip, cell, db, cell->concepts);
+}
+
+
+bool pmConceptsInit(void)
+{
+    bool init = false;                  // Did we initialise anything?
+    if (! conceptsFPA) {
+        conceptsFPA = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+
+        // FPA.NAME
+        {
+            psMetadataItem *fpaName = psMetadataItemAllocStr("FPA.NAME", "Name of FPA", "");
+            pmConceptRegister(fpaName, NULL, NULL, PM_CONCEPT_LEVEL_FPA);
+            psFree(fpaName);
+        }
+
+        // FPA.AIRMASS
+        {
+            psMetadataItem *fpaAirmass = psMetadataItemAllocF32("FPA.AIRMASS", "Airmass at boresight", 0.0);
+            pmConceptRegister(fpaAirmass, NULL, NULL, PM_CONCEPT_LEVEL_FPA);
+            psFree(fpaAirmass);
+        }
+
+        // FPA.FILTER
+        {
+            psMetadataItem *fpaFilter = psMetadataItemAllocStr("FPA.FILTER", "Filter used", "");
+            pmConceptRegister(fpaFilter, NULL, NULL, PM_CONCEPT_LEVEL_FPA);
+            psFree(fpaFilter);
+        }
+
+        // FPA.POSANGLE
+        {
+            psMetadataItem *fpaPosangle = psMetadataItemAllocF32("FPA.POSANGLE",
+                                          "Position angle of instrument", 0.0);
+            pmConceptRegister(fpaPosangle, NULL, NULL, PM_CONCEPT_LEVEL_FPA);
+            psFree(fpaPosangle);
+        }
+
+        // FPA.RADECSYS
+        {
+            psMetadataItem *fpaRadecsys = psMetadataItemAllocStr("FPA.RADECSYS",
+                                          "Celestial coordinate system", "");
+            pmConceptRegister(fpaRadecsys, NULL, NULL, PM_CONCEPT_LEVEL_FPA);
+            psFree(fpaRadecsys);
+        }
+
+        // FPA.RA
+        {
+            psMetadataItem *fpaRa = psMetadataItemAllocF64("FPA.RA", "Right Ascension of boresight", NAN);
+            pmConceptRegister(fpaRa, (pmConceptReadFunc)pmConceptRead_FPA_RA,
+                              (pmConceptWriteFunc)pmConceptWrite_FPA_RA, PM_CONCEPT_LEVEL_FPA);
+            psFree(fpaRa);
+        }
+
+        // FPA.DEC
+        {
+            psMetadataItem *fpaDec = psMetadataItemAllocF64("FPA.DEC", "Declination of boresight", NAN);
+            pmConceptRegister(fpaDec, (pmConceptReadFunc)pmConceptRead_FPA_DEC,
+                              (pmConceptWriteFunc)pmConceptWrite_FPA_DEC, PM_CONCEPT_LEVEL_FPA);
+            psFree(fpaDec);
+        }
+
+        // Done with FPA level concepts
+    }
+    if (! conceptsChip) {
+        conceptsChip = psMetadataAlloc();
+        init = true;
+        // There are no standard concepts at the chip level to be installed
+    }
+    if (! conceptsCell) {
+        conceptsCell = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+
+        // CELL.GAIN
+        {
+            psMetadataItem *cellGain = psMetadataItemAllocF32("CELL.GAIN", "CCD gain (e/count)", NAN);
+            pmConceptRegister(cellGain, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellGain);
+        }
+
+        // CELL.READNOISE
+        {
+            psMetadataItem *cellReadnoise = psMetadataItemAllocF32("CELL.READNOISE",
+                                            "CCD read noise (e)", NAN);
+            pmConceptRegister(cellReadnoise, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellReadnoise);
+        }
+
+        // CELL.SATURATION
+        {
+            psMetadataItem *cellSaturation = psMetadataItemAllocF32("CELL.SATURATION",
+                                             "Saturation level (counts)", NAN);
+            pmConceptRegister(cellSaturation, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellSaturation);
+        }
+
+        // CELL.BAD
+        {
+            psMetadataItem *cellBad = psMetadataItemAllocF32("CELL.BAD", "Bad level (counts)", NAN);
+            pmConceptRegister(cellBad, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellBad);
+        }
+
+        // CELL.XPARITY
+        {
+            psMetadataItem *cellXparity = psMetadataItemAllocS32("CELL.XPARITY",
+                                          "Orientation in x compared to the rest of the FPA", 0);
+            pmConceptRegister(cellXparity, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellXparity);
+        }
+
+        // CELL.YPARITY
+        {
+            psMetadataItem *cellYparity = psMetadataItemAllocS32("CELL.YPARITY",
+                                          "Orientation in x compared to the rest of the FPA", 0);
+            pmConceptRegister(cellYparity, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellYparity);
+        }
+
+        // CELL.READDIR
+        {
+            psMetadataItem *cellReaddir = psMetadataItemAllocS32("CELL.READDIR",
+                                          "Read direction, rows=1, cols=2", 1);
+            pmConceptRegister(cellReaddir, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellReaddir);
+        }
+
+
+        // These (CELL.EXPOSURE and CELL.DARKTIME) used to be READOUT.EXPOSURE and READOUT.DARKTIME, but that
+        // doesn't really make sense at the moment.  Maybe we need to add a "parent" link to the readouts.
+        // But then how are the exposure times REALLY derived?  They're not in the FITS headers, because a
+        // readout is a plane in a 3D image.  We'll have to dream up some additional suffix to specify these,
+        // but for now....
+
+        // CELL.EXPOSURE
+        {
+            psMetadataItem *cellExposure = psMetadataItemAllocF32("CELL.EXPOSURE",
+                                           "Exposure time (sec)", NAN);
+            pmConceptRegister(cellExposure, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellExposure);
+        }
+
+        // CELL.DARKTIME
+        {
+            psMetadataItem *cellDarktime = psMetadataItemAllocF32("CELL.DARKTIME",
+                                           "Time since flush (sec)", NAN);
+            pmConceptRegister(cellDarktime, NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellDarktime);
+        }
+
+        // CELL.TRIMSEC
+        {
+            psRegion *trimsec = psAlloc(sizeof(psRegion)); // Blank trimsec
+            trimsec->x0 = trimsec->y0 = trimsec->x1 = trimsec->y1 = NAN;
+            psMetadataItem *cellTrimsec = psMetadataItemAllocPtr("CELL.TRIMSEC", PS_DATA_UNKNOWN,
+                                          "Trim section", trimsec);
+            psFree(trimsec);
+            pmConceptRegister(cellTrimsec, (pmConceptReadFunc)pmConceptRead_CELL_TRIMSEC,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_TRIMSEC, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellTrimsec);
+        }
+
+        // CELL.BIASSEC
+        {
+            psList *biassecs = psListAlloc(NULL); // Blank biassecs
+            psMetadataItem *cellBiassec = psMetadataItemAllocPtr("CELL.BIASSEC", PS_DATA_LIST,
+                                          "Bias sections", biassecs);
+            psFree(biassecs);
+            pmConceptRegister(cellBiassec, (pmConceptReadFunc)pmConceptRead_CELL_BIASSEC,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_BIASSEC, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellBiassec);
+        }
+
+        // CELL.XBIN
+        {
+            psMetadataItem *cellXbin = psMetadataItemAllocS32("CELL.XBIN", "Binning in x", 0);
+            pmConceptRegister(cellXbin, (pmConceptReadFunc)pmConceptRead_CELL_XBIN,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_XBIN, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellXbin);
+        }
+
+        // CELL.YBIN
+        {
+            psMetadataItem *cellYbin = psMetadataItemAllocS32("CELL.YBIN", "Binning in y", 0);
+            pmConceptRegister(cellYbin, (pmConceptReadFunc)pmConceptRead_CELL_YBIN,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_YBIN, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellYbin);
+        }
+
+        // CELL.TIMESYS
+        {
+            psMetadataItem *cellTimesys = psMetadataItemAllocS32("CELL.TIMESYS", "Time system", -1);
+            pmConceptRegister(cellTimesys, (pmConceptReadFunc)pmConceptRead_CELL_TIMESYS,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_TIMESYS, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellTimesys);
+        }
+
+        // CELL.TIME
+        {
+            psTime *time = psTimeAlloc(PS_TIME_TAI); // Blank time
+            // Not particularly distinguishing, but should be good enough
+            time->sec = 0;
+            time->nsec = 0;
+            psMetadataItem *cellTime = psMetadataItemAlloc("CELL.TIME", PS_DATA_TIME,
+                                       "Time of exposure", time);
+            psFree(time);
+            pmConceptRegister(cellTime, (pmConceptReadFunc)pmConceptRead_CELL_TIME,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_TIME, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellTime);
+        }
+
+        // CELL.X0
+        {
+            psMetadataItem *cellX0 = psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip", 0);
+            pmConceptRegister(cellX0, (pmConceptReadFunc)pmConceptRead_CELL_X0,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_X0, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellX0);
+        }
+
+        // CELL.Y0
+        {
+            psMetadataItem *cellY0 = psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip", 0);
+            pmConceptRegister(cellY0, (pmConceptReadFunc)pmConceptRead_CELL_Y0,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_Y0, PM_CONCEPT_LEVEL_CELL);
+            psFree(cellY0);
+        }
+
+    }
+
+    conceptsInitialised = true;
+
+    return init;
+}
+
+void pmConceptsDone(void)
+{
+    psFree(conceptsFPA);
+    psFree(conceptsChip);
+    psFree(conceptsCell);
+}
+
+
+// Copy concepts from one FPA to another
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      )
+{
+    // Copy FPA concepts
+    target->concepts = pap_psMetadataCopy(target->concepts, source->concepts);
+
+    // Copy chip concepts
+    psArray *targetChips = target->chips; // Chips in target
+    psArray *sourceChips = source->chips; // Chips in source
+    if (targetChips->n != sourceChips->n) {
+        psError(PS_ERR_IO, true, "Number of chips in target (%d) and source (%d) differ --- unable to copy "
+                "concepts.\n", targetChips->n, sourceChips->n);
+        return false;
+    }
+    for (int i = 0; i < targetChips->n; i++) {
+        pmChip *targetChip = targetChips->data[i]; // Target chip of interest
+        pmChip *sourceChip = sourceChips->data[i]; // Source chip of interest
+        if (! targetChip || ! sourceChip) {
+            continue;
+        }
+        targetChip->concepts = pap_psMetadataCopy(targetChip->concepts, sourceChip->concepts);
+
+        // Copy cell concepts
+        psArray *targetCells = targetChip->cells; // Cells in target
+        psArray *sourceCells = sourceChip->cells; // Cells in source
+        if (targetCells->n != sourceCells->n) {
+            psError(PS_ERR_IO, true, "Number of cells in target (%d) and source (%d) differ for chip %d ---"
+                    " unable to copy concepts.\n", targetCells->n, sourceCells->n, i);
+            return false;
+        }
+        for (int j = 0; j < targetCells->n; j++) {
+            pmCell *targetCell = targetCells->data[j]; // Target chip of interest
+            pmCell *sourceCell = sourceCells->data[j]; // Source chip of interest
+            if (! targetCell || ! sourceCell) {
+                continue;
+            }
+            targetCell->concepts = pap_psMetadataCopy(targetCell->concepts, sourceCell->concepts);
+        }
+    }
+
+    return true;
+}
Index: /branches/rel10_ifa/psModules/src/astrom/pmConcepts.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConcepts.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConcepts.h	(revision 6448)
@@ -0,0 +1,79 @@
+#ifndef PM_CONCEPTS_H
+#define PM_CONCEPTS_H
+
+#include "pslib.h"
+
+#include "pmFPA.h"
+
+// Function to call to read a concept
+typedef psMetadataItem* (*pmConceptReadFunc)(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+// Function to call to write a concept
+typedef bool (*pmConceptWriteFunc)(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+
+// A "concept" specification
+typedef struct
+{
+    psMetadataItem *blank;              // Blank value of concept; also contains the name
+    pmConceptReadFunc read;         // Function to call to read the concept
+    pmConceptWriteFunc write;       // Function to call to write the concept
+}
+pmConceptSpec;
+
+// Allocator
+pmConceptSpec *pmConceptSpecAlloc(psMetadataItem *blank, // Blank value; contains the name
+                                  pmConceptReadFunc read, // Function to call to read the concept
+                                  pmConceptWriteFunc write // Function to call to write the concept
+                                 );
+
+// Level at which to store a concept in the FPA hierarchy
+typedef enum {
+    PM_CONCEPT_LEVEL_FPA,               // Store in the FPA
+    PM_CONCEPT_LEVEL_CHIP,              // Store in the chip
+    PM_CONCEPT_LEVEL_CELL               // Store in the cell
+} pmConceptLevel;
+
+// Register a new concept
+bool pmConceptRegister(psMetadataItem *blank, // Blank value; contains the name
+                       pmConceptReadFunc read, // Function to call to read the concept
+                       pmConceptWriteFunc write, // Function to call to write the concept
+                       pmConceptLevel level // Level at which to store concept in the FPA hierarchy
+                      );
+
+// Set blanks, read or write concepts at the appropriate level
+bool pmConceptsBlankFPA(pmFPA *fpa    // FPA for which to set blank concepts
+                       );
+bool pmConceptsReadFPA(pmFPA *fpa,      // FPA for which to read concepts
+                       psDB *db         // Database handle
+                      );
+bool pmConceptsWriteFPA(pmFPA *fpa,     // FPA for which to write concepts
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsBlankChip(pmChip *chip // FPA for which to set blank concepts
+                        );
+bool pmConceptsReadChip(pmChip *chip,   // Chip for which to read concepts
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsWriteChip(pmChip *chip,  // Chip for which to write concepts
+                         psDB *db        // Database handle
+                        );
+bool pmConceptsBlankCell(pmCell *cell // Cell for which to set blank concepts
+                        );
+bool pmConceptsReadCell(pmCell *cell,   // Cell for which to read concepts
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsWriteCell(pmCell *cell,  // FPA for which to write concepts
+                         psDB *db       // Database handle
+                        );
+
+// Set up the blank concepts
+bool pmConceptsInit(void);
+// Free the concept specs so there's no memory leak
+void pmConceptsDone(void);
+
+// Copy all the concepts within an FPA to another FPA
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      );
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.c	(revision 6448)
@@ -0,0 +1,387 @@
+#include <stdio.h>
+
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmConceptsRead.h"
+#include "psAdditionals.h"
+
+
+psMetadataItem *pmConceptReadFromCamera(pmCell *cell, // The cell
+                                        const char *concept // Name of concept
+                                       )
+{
+    if (cell) {
+        psMetadata *camera = cell->camera;      // Camera data
+        psMetadataItem *item = psMetadataLookup(camera, concept);
+        return item;
+    }
+    return NULL;
+}
+
+psMetadataItem *pmConceptReadFromHeader(pmFPA *fpa, // The FPA that contains the chip
+                                        pmChip *chip, // The chip that contains the cell
+                                        pmCell *cell, // The cell
+                                        const char *concept // Name of concept
+                                       )
+{
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *translation = psMetadataLookupMD(&mdStatus, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdStatus) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return NULL;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdStatus, translation, concept);
+    if (mdStatus && strlen(keyword) > 0) {
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psMetadataItem *cellItem = psMetadataLookup(cell->hdu->header, keyword);
+            if (cellItem) {
+                // XXX: Need to clean up before returning
+                return cellItem;
+            }
+        }
+
+        if (chip && chip->hdu) {
+            psMetadataItem *chipItem = psMetadataLookup(chip->hdu->header, keyword);
+            if (chipItem) {
+                // XXX: Need to clean up before returning
+                return chipItem;
+            }
+        }
+
+        if (fpa->hdu) {
+            psMetadataItem *fpaItem = psMetadataLookup(fpa->hdu->header, keyword);
+            if (fpaItem) {
+                // XXX: Need to clean up before returning
+                return fpaItem;
+            }
+        }
+
+        if (fpa->phu) {
+            psMetadataItem *fpaItem = psMetadataLookup(fpa->phu, keyword);
+            if (fpaItem) {
+                // XXX: Need to clean up before returning
+                return fpaItem;
+            }
+        }
+    }
+
+    // No header value
+    return NULL;
+}
+
+
+// Look for a default
+psMetadataItem *pmConceptReadFromDefault(pmFPA *fpa, // The FPA that contains the chip
+        pmChip *chip, // The chip that contains the cell
+        pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                        )
+{
+    bool mdOK = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return NULL;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept);
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return NULL;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = pmConceptReadFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept);
+                return NULL;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V);    // This is now what we were after
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return defItem;                     // defItem is either NULL or points to what was desired
+}
+
+
+// Look for a database lookup
+// XXX: Not tested
+psMetadataItem *pmConceptReadFromDB(pmFPA *fpa, // The FPA that contains the chip
+                                    pmChip *chip, // The chip that contains the cell
+                                    pmCell *cell, // The cell
+                                    psDB *db, // DB handle
+                                    const char *concept // Name of concept
+                                   )
+{
+    if (! db) {
+        // No database initialised
+        return NULL;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        // const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // List of value names for the columns
+            psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
+            if (cols->n != values->n) {
+                psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
+                         "the same number of entries --- ignored.\n", concept);
+            } else {
+                // Iterators for the lists
+                psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
+                psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+                char *column = NULL;    // Name of the column
+                while ((column = psListGetAndIncrement(colsIter))) {
+                    char *name = psListGetAndIncrement(valuesIter); // Name for the value
+                    if (!strlen(column) || !strlen(name)) {
+                        psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
+                                 " empty --- ignored.\n", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = pmConceptReadFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = pmConceptReadFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } else {
+                            // We need to create a new psMetadataItem.  I don't think we can't simply hack
+                            // the existing one, since that could conceivably cause memory leaks
+                            psMetadataItem *newItem = psMetadataItemAlloc(concept, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    psFree(column);
+                } // Iterating through the columns
+                psFree(colsIter);
+                psFree(valuesIter);
+
+                psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
+                // Note that we use limit=2 in order to test if there are multiple rows returned
+
+                psMetadataItem *result = NULL; // The final result of the DB lookup
+                if (dbResult->n == 0) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n",
+                             concept);
+                } else {
+                    if (dbResult-> n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " using the first one only.\n", concept);
+                    }
+                    result = (psMetadataItem*)dbResult->data[0];
+                }
+                // XXX: Need to clean up before returning
+                return result;
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here.\n");
+    return NULL;
+}
+
+
+// Concept lookup
+psMetadataItem *pmConceptRead(pmFPA *fpa, // The FPA
+                              pmChip *chip,// The chip
+                              pmCell *cell, // The cell
+                              psDB *db, // DB handle
+                              const char *name // Concept name
+                             )
+{
+    // Try headers, database, defaults in order
+    psMetadataItem *item = pmConceptReadFromCamera(cell, name);
+    if (! item) {
+        item = pmConceptReadFromHeader(fpa, chip, cell, name);
+    }
+    if (! item) {
+        item = pmConceptReadFromDB(fpa, chip, cell, db, name);
+    }
+    if (! item) {
+        item = pmConceptReadFromDefault(fpa, chip, cell, name);
+    }
+    return item; // item is either NULL, or points to what was desired
+}
+
+
+float pmConceptReadF32(pmFPA *fpa,        // The FPA
+                       pmChip *chip,      // The chip
+                       pmCell *cell,      // The cell
+                       psDB *db,          // DB handle
+                       const char *name // Name of the concept
+                      )
+{
+    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
+    float value = NAN;
+    if (item) {
+        switch (item->type) {
+        case PS_DATA_F32:
+            value = item->data.F32;
+            break;
+        case PS_DATA_F64:
+            // Assume it's OK to truncate to floating point from double
+            value = (float)item->data.F64;
+            break;
+        case PS_DATA_S32:
+            // Promote to float
+            value = (float)item->data.S32;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
+                    "undefined.\n", name, item->comment, item->type);
+        }
+        psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+    }
+
+    return value;
+}
+
+double pmConceptReadF64(pmFPA *fpa,   // The FPA
+                        pmChip *chip, // The chip
+                        pmCell *cell, // The cell
+                        psDB *db,     // DB handle
+                        const char *name // Name of the concept
+                       )
+{
+    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
+    double value = NAN;
+    if (item) {
+        switch (item->type) {
+        case PS_TYPE_F64:
+            value = item->data.F64;
+            break;
+        case PS_TYPE_F32:
+            // Promote to double
+            value = (double)item->data.F32;
+            break;
+        case PS_TYPE_S32:
+            // Promote to double
+            value = (double)item->data.S32;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
+                    "--- treating as undefined.\n", name, item->comment, item->type);
+        }
+        psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+    }
+
+    return value;
+}
+
+int pmConceptReadS32(pmFPA *fpa,   // The FPA
+                     pmChip *chip, // The chip
+                     pmCell *cell, // The cell
+                     psDB *db,     // DB handle
+                     const char *name // Name of the concept
+                    )
+{
+    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
+    int value = 0;
+    if (item) {
+        switch (item->type) {
+        case PS_TYPE_S32:
+            value = item->data.S32;
+            break;
+        case PS_TYPE_F32:
+            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
+                     name, comment);
+            value = (int)item->data.F32;
+            break;
+        case PS_TYPE_F64:
+            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
+                     name, comment);
+            value = (int)item->data.F64;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
+                    "undefined.\n", name, item->comment, item->type);
+        }
+        psTrace(__func__, 7, "Read concept %s (%s): %d\n", name, item->comment, value);
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+    }
+
+    return value;
+}
+
+psString pmConceptReadString(pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psDB *db,  // DB handle
+                             const char *name // Name of the concept
+                            )
+{
+    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
+    psString value = NULL;
+    if (item) {
+        switch (item->type) {
+        case PS_DATA_STRING:
+            value = psMemIncrRefCounter(item->data.V);
+            break;
+        case PS_DATA_F32:
+            psStringAppend(&value, "%f", item->data.F32);
+            break;
+        case PS_DATA_S32:
+            psStringAppend(&value, "%d", item->data.S32);
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
+                    "undefined.\n", name, item->comment, item->type);
+        }
+        psTrace(__func__, 7, "Read concept %s (%s): %s\n", name, item->comment, value);
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+        value = psStringCopy("");
+    }
+
+    return value;
+}
+
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.h	(revision 6448)
@@ -0,0 +1,65 @@
+#ifndef PM_CONCEPTS_READ_H
+#define PM_CONCEPTS_READ_H
+
+#include "pmFPA.h"
+
+psMetadataItem *pmConceptReadFromCamera(pmCell *cell, // The cell
+                                        const char *concept // Name of concept
+                                       );
+
+psMetadataItem *pmConceptReadFromHeader(pmFPA *fpa, // The FPA that contains the chip
+                                        pmChip *chip, // The chip that contains the cell
+                                        pmCell *cell, // The cell
+                                        const char *concept // Name of concept
+                                       );
+
+psMetadataItem *pmConceptReadFromDefault(pmFPA *fpa, // The FPA that contains the chip
+        pmChip *chip, // The chip that contains the cell
+        pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                        );
+
+psMetadataItem *pmConceptReadFromDB(pmFPA *fpa, // The FPA that contains the chip
+                                    pmChip *chip, // The chip that contains the cell
+                                    pmCell *cell, // The cell
+                                    psDB *db, // DB handle
+                                    const char *concept // Name of concept
+                                   );
+
+psMetadataItem *pmConceptRead(pmFPA *fpa, // The FPA
+                              pmChip *chip,// The chip
+                              pmCell *cell, // The cell
+                              psDB *db, // DB handle
+                              const char *concept // Concept name
+                             );
+
+float pmConceptReadF32(pmFPA *fpa,        // The FPA
+                       pmChip *chip,      // The chip
+                       pmCell *cell,      // The cell
+                       psDB *db,          // DB handle
+                       const char *name // Name of the concept
+                      );
+
+double pmConceptReadF64(pmFPA *fpa,   // The FPA
+                        pmChip *chip, // The chip
+                        pmCell *cell, // The cell
+                        psDB *db,     // DB handle
+                        const char *name // Name of the concept
+                       );
+
+int pmConceptReadS32(pmFPA *fpa,   // The FPA
+                     pmChip *chip, // The chip
+                     pmCell *cell, // The cell
+                     psDB *db,     // DB handle
+                     const char *name // Name of the concept
+                    );
+
+psString pmConceptReadString(pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psDB *db,  // DB handle
+                             const char *name // Name of the concept
+                            );
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.c	(revision 6448)
@@ -0,0 +1,1017 @@
+#include <stdio.h>
+
+#include "pslib.h"
+
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmFPA.h"
+#include "pmConceptsStandard.h"
+#include "psAdditionals.h"
+
+
+#define COMPARE_REGIONS(a,b) (((a)->x0 == (b)->x0 && \
+                               (a)->x1 == (b)->x1 && \
+                               (a)->y0 == (b)->y0 && \
+                               (a)->y1 == (b)->y1) ? true : false)
+
+
+// Read FPA.RA and translate
+psMetadataItem *pmConceptRead_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    double ra = NAN;                    // The Right Ascension
+    psMetadataItem *raItem = pmConceptRead(fpa, NULL, NULL, db, "FPA.RA"); // The FPA.RA item
+    if (raItem) {
+        switch (raItem->type) {
+        case PS_TYPE_F32:
+            ra = raItem->data.F32;
+            break;
+        case PS_TYPE_F64:
+            ra = raItem->data.F64;
+            break;
+        case PS_DATA_STRING:
+            // Sexagesimal format
+            {
+                int big, medium;
+                float small;
+                // XXX: Upgrade path is to allow dd:mm.mmm
+                if (sscanf(raItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                        sscanf(raItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
+                {
+                    psError(PS_ERR_IO, true, "Cannot interpret FPA.RA: %s\n", raItem->data.V);
+                    break;
+                }
+                ra = abs(big) + (float)medium/60.0 + small/3600.0;
+                if (big < 0)
+                {
+                    ra *= -1.0;
+                }
+            }
+            break;
+        default:
+            psError(PS_ERR_IO, true, "FPA.RA is of an unexpected type: %x\n", raItem->type);
+        }
+
+        // How to interpret the RA
+        const psMetadata *camera = fpa->camera; // Camera configuration data
+        bool mdok = true;           // Status of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats) {
+            psString raFormat = psMetadataLookupStr(&mdok, formats, "FPA.RA");
+            if (mdok && strlen(raFormat) > 0) {
+                if (strcasecmp(raFormat, "HOURS") == 0) {
+                    ra *= M_PI / 12.0;
+                } else if (strcasecmp(raFormat, "DEGREES") == 0) {
+                    ra *= M_PI / 180.0;
+                } else if (strcasecmp(raFormat, "RADIANS") == 0) {
+                    // No action required
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
+                             " HOURS.\n");
+                    ra *= M_PI / 12.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
+                ra *= M_PI / 12.0;
+            }
+        } else {
+            psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                    "assuming format for FPA.RA is HOURS.\n");
+            ra *= M_PI / 12.0;
+        }
+    } else {
+        psError(PS_ERR_IO, false, "Couldn't find FPA.RA.\n");
+    }
+
+    return psMetadataItemAllocF64("FPA.RA", "Right Ascension of boresight", ra);
+}
+
+// Read FPA.DEC and translate
+psMetadataItem *pmConceptRead_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    double dec = NAN;               // The DEC
+    psMetadataItem *decItem = pmConceptRead(fpa, NULL, NULL, db, "FPA.DEC"); // The FPA.DEC item
+    if (decItem) {
+        switch (decItem->type) {
+        case PS_TYPE_F32:
+            dec = decItem->data.F32;
+            break;
+        case PS_TYPE_F64:
+            dec = decItem->data.F64;
+            break;
+        case PS_DATA_STRING:
+            // Sexagesimal format
+            {
+                int big, medium;
+                float small;
+                // XXX: Upgrade path is to allow dd:mm.mmm
+                if (sscanf(decItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                        sscanf(decItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
+                {
+                    psError(PS_ERR_IO, true, "Cannot interpret FPA.DEC: %s\n", decItem->data.V);
+                    break;
+                }
+                dec = abs(big) + (float)medium/60.0 + small/3600.0;
+                if (big < 0)
+                {
+                    dec *= -1.0;
+                }
+            }
+            break;
+        default:
+            psError(PS_ERR_IO, true, "FPA.DEC is of an unexpected type: %x\n", decItem->type);
+        }
+
+        // How to interpret the DEC
+        const psMetadata *camera = fpa->camera; // Camera configuration data
+        bool mdok = true;           // Status of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats) {
+            psString decFormat = psMetadataLookupStr(&mdok, formats, "FPA.DEC");
+            if (mdok && strlen(decFormat) > 0) {
+                if (strcasecmp(decFormat, "HOURS") == 0) {
+                    dec *= M_PI / 12.0;
+                } else if (strcasecmp(decFormat, "DEGREES") == 0) {
+                    dec *= M_PI / 180.0;
+                } else if (strcasecmp(decFormat, "RADIANS") == 0) {
+                    // No action required
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- "
+                             "assuming DEGREES.\n");
+                    dec *= M_PI / 180.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
+                dec *= M_PI / 180.0;
+            }
+        } else {
+            psError(PS_ERR_IO, false, "Unable to find FORMATS metadata in camera configuration --- "
+                    "assuming format for FPA.DEC is DEGREES.\n");
+            dec *= M_PI / 180.0;
+        }
+    } else {
+        psError(PS_ERR_IO, false, "Couldn't find FPA.DEC.\n");
+    }
+
+    return psMetadataItemAllocF64("FPA.DEC", "Declination of boresight", dec);
+}
+
+
+bool pmConceptWrite_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    double ra = psMetadataLookupF64(NULL, fpa->concepts, "FPA.RA"); // The RA
+
+    // How to interpret the RA
+    const psMetadata *camera = fpa->camera; // Camera configuration data
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+    if (mdok && formats) {
+        psString raFormat = psMetadataLookupStr(&mdok, formats, "FPA.RA");
+        if (mdok && strlen(raFormat) > 0) {
+            if (strcasecmp(raFormat, "HOURS") == 0) {
+                ra /= M_PI / 12.0;
+            } else if (strcasecmp(raFormat, "DEGREES") == 0) {
+                ra /= M_PI / 180.0;
+            } else if (strcasecmp(raFormat, "RADIANS") == 0) {
+                // No action required
+            } else {
+                psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
+                         " HOURS.\n");
+                ra /= M_PI / 12.0;
+            }
+        } else {
+            psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
+            ra /= M_PI / 12.0;
+        }
+    } else {
+        psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                "assuming format for FPA.RA is HOURS.\n");
+        ra /= M_PI / 12.0;
+    }
+
+    // We choose to write sexagesimal format
+    int big, medium;
+    float small;
+    big = (int)ra;
+    medium = (int)(60.0*(ra - (double)big));
+    small = 3600.0*(ra - (double)big - 60.0 * (double)medium);
+    psString raString = psStringCopy("");
+    psStringAppend(&raString, "%d:%d:%.2f", big, medium, small);
+    psMetadataItem *raItem = psMetadataItemAllocStr("FPA.RA", "Right Ascension of the boresight",
+                             raString);
+    pmConceptWriteItem(fpa, NULL, NULL, db, raItem);
+    psFree(raItem);
+    psFree(raString);
+
+    return true;
+}
+
+bool pmConceptWrite_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    double dec = psMetadataLookupF64(NULL, fpa->concepts, "FPA.DEC"); // The DEC
+
+    // How to interpret the DEC
+    const psMetadata *camera = fpa->camera; // Camera configuration data
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+    if (mdok && formats) {
+        psString decFormat = psMetadataLookupStr(&mdok, formats, "FPA.DEC");
+        if (mdok && strlen(decFormat) > 0) {
+            if (strcasecmp(decFormat, "HOURS") == 0) {
+                dec /= M_PI / 12.0;
+            } else if (strcasecmp(decFormat, "DEGREES") == 0) {
+                dec /= M_PI / 180.0;
+            } else if (strcasecmp(decFormat, "RADIANS") == 0) {
+                // No action required
+            } else {
+                psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- assuming"
+                         " DEGREES.\n");
+                dec /= M_PI / 180.0;
+            }
+        } else {
+            psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
+            dec /= M_PI / 180.0;
+        }
+    } else {
+        psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                "assuming format for FPA.DEC is HOURS.\n");
+        dec /= M_PI / 12.0;
+    }
+
+    // We choose to write sexagesimal format
+    int big, medium;
+    float small;
+    big = (int)dec;
+    medium = (int)(60.0*(dec - (double)big));
+    small = 3600.0*(dec - (double)big - 60.0 * (double)medium);
+    psString decString = psStringCopy("");
+    psStringAppend(&decString, "%d:%d:%.2f", big, medium, small);
+    psMetadataItem *decItem = psMetadataItemAllocStr("FPA.DEC", "Right Ascension of the boresight",
+                              decString);
+    pmConceptWriteItem(fpa, NULL, NULL, db, decItem);
+    psFree(decItem);
+    psFree(decString);
+
+    return true;
+}
+
+
+psMetadataItem *pmConceptRead_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psRegion *trimsec = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by value)
+
+    psMetadataItem *secItem = pmConceptRead(fpa, chip, cell, db, "CELL.TRIMSEC");
+    if (! secItem) {
+        psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.\n");
+        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+    } else if (secItem->type != PS_DATA_STRING) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not of type STR (%x)\n", secItem->type);
+        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+    } else {
+        psString section = secItem->data.V; // The section string
+
+        psMetadataItem *sourceItem = pmConceptRead(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
+        if (! sourceItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
+            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+        } else if (sourceItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+        } else {
+            psString source = sourceItem->data.V; // The source string
+
+            if (strcasecmp(source, "VALUE") == 0) {
+                *trimsec = psRegionFromString(section);
+            } else if (strcasecmp(source, "HEADER") == 0) {
+                psMetadata *header = NULL; // The FITS header
+                if (cell->hdu) {
+                    header = cell->hdu->header;
+                } else if (chip->hdu) {
+                    header = chip->hdu->header;
+                } else if (fpa->hdu) {
+                    header = fpa->hdu->header;
+                }
+                if (! header) {
+                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                    *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+                } else {
+                    bool mdok = true;               // Status of MD lookup
+                    psString secValue = psMetadataLookupStr(&mdok, header, section);
+                    if (! mdok || ! secValue) {
+                        psError(PS_ERR_IO, false, "Unable to locate header %s\n", section);
+                        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+                    } else {
+                        *trimsec = psRegionFromString(secValue);
+                    }
+                }
+            } else {
+                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
+                        "VALUE.\n", source);
+                *trimsec = psRegionFromString(section);
+            } // Value of CELL.TRIMSEC.SOURCE
+        } // Looking up CELL.TRIMSEC.SOURCE
+    } // Looking up CELL.TRIMSEC
+
+    psMetadataItem *item = psMetadataItemAllocPtr("CELL.TRIMSEC", PS_DATA_UNKNOWN, "Trim section", trimsec);
+    psFree(trimsec);
+    return item;
+}
+
+
+psMetadataItem *pmConceptRead_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psList *biassecs = psListAlloc(NULL); // List of bias sections
+    psMetadataItem *item = psMetadataItemAlloc("CELL.BIASSEC", PS_DATA_LIST, "Bias sections", biassecs);
+    psFree(biassecs);               // Drop reference
+
+    psMetadataItem *secItem = pmConceptRead(fpa, chip, cell, db, "CELL.BIASSEC");
+    if (! secItem) {
+        psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.\n");
+        return item;
+    }
+    if (secItem->type != PS_DATA_STRING) {
+        psError(PS_ERR_IO, true, "CELL.BIASSEC is not of type STR (%x)\n", secItem->type);
+        return item;
+    }
+
+    psString sections = secItem->data.V; // The section string
+
+    psMetadataItem *sourceItem = pmConceptRead(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
+    if (! sourceItem) {
+        psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
+        return item;
+    } else if (sourceItem->type != PS_DATA_STRING) {
+        psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+        return item;
+    }
+
+    psString source = sourceItem->data.V; // The source string
+
+    if (! strcasecmp(source, "NONE")) {
+        return item;                // There is no biassec
+    }
+
+    psList *secList = psStringSplit(sections, " ;"); // List of sections
+    psListIterator *secIter = psListIteratorAlloc(secList, PS_LIST_HEAD, false); // Iterator over
+    // sections
+    psString aSection = NULL; // A section from the list
+    while ((aSection = psListGetAndIncrement(secIter))) {
+        psRegion *region = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by
+        // value)
+
+        if (strcasecmp(source, "VALUE") == 0) {
+            *region = psRegionFromString(aSection);
+        } else if (strcasecmp(source, "HEADER") == 0) {
+            psMetadata *header = NULL; // The FITS header
+            if (cell->hdu) {
+                header = cell->hdu->header;
+            } else if (chip->hdu) {
+                header = chip->hdu->header;
+            } else if (fpa->hdu) {
+                header = fpa->hdu->header;
+            }
+            if (! header) {
+                psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                *region = psRegionSet(0.0,0.0,0.0,0.0);
+            } else {
+                bool mdok = true;           // Status of MD lookup
+                psString secValue = psMetadataLookupStr(&mdok, header, aSection);
+                if (! mdok || ! secValue) {
+                    psError(PS_ERR_IO, false, "Unable to locate header %s\n", aSection);
+                    *region = psRegionSet(0.0,0.0,0.0,0.0);
+                } else {
+                    *region = psRegionFromString(secValue);
+                }
+            }
+        } else {
+            psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
+                    "VALUE.\n", source);
+            *region = psRegionFromString(aSection);
+        } // Value of CELL.BIASSEC.SOURCE
+
+        psListAdd(biassecs, PS_LIST_TAIL, region);
+        psFree(region);
+    } // Iterating over multiple sections
+    psFree(secIter);
+    psFree(secList);
+
+    return item;
+}
+
+
+psMetadataItem *pmConceptRead_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    int xBin = 1;                   // Binning factor in x
+    psMetadataItem *binItem = pmConceptRead(fpa, chip, cell, db, "CELL.XBIN");
+    if (! binItem) {
+        psError(PS_ERR_IO, false, "Couldn't find CELL.XBIN.\n");
+    } else if (binItem->type == PS_DATA_STRING) {
+        psString binString = binItem->data.V; // The string containing the binning
+        if (sscanf(binString, "%d %*d", &xBin) != 1 &&
+                sscanf(binString, "%d,%*d", &xBin) != 1) {
+            psError(PS_ERR_IO, true, "Unable to read string to get x binning: %s\n", binString);
+        }
+    } else if (binItem->type == PS_TYPE_S32) {
+        xBin = binItem->data.S32;
+    } else {
+        psError(PS_ERR_IO, true, "Note sure how to interpret CELL.XBIN of type %x --- assuming 1.\n",
+                binItem->type);
+    }
+
+    return psMetadataItemAllocS32("CELL.XBIN", "Binning in x", xBin);
+}
+
+psMetadataItem *pmConceptRead_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    int yBin = 1;                   // Binning factor in y
+    psMetadataItem *binItem = pmConceptRead(fpa, chip, cell, db, "CELL.YBIN");
+    if (! binItem) {
+        psError(PS_ERR_IO, false, "Couldn't find CELL.YBIN.\n");
+    } else if (binItem->type == PS_DATA_STRING) {
+        psString binString = binItem->data.V; // The string containing the binning
+        if (sscanf(binString, "%*d %d", &yBin) != 1 &&
+                sscanf(binString, "%*d,%d", &yBin) != 1) {
+            psError(PS_ERR_IO, true, "Unable to read string to get y binning: %s\n", binString);
+        }
+    } else if (binItem->type == PS_TYPE_S32) {
+        yBin = binItem->data.S32;
+    } else {
+        psError(PS_ERR_IO, true, "Note sure how to interpret CELL.YBIN of type %x --- assuming 1.\n",
+                binItem->type);
+    }
+
+    return psMetadataItemAllocS32("CELL.YBIN", "Binning in y", yBin);
+}
+
+psMetadataItem *pmConceptRead_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psTimeType timeSys = PS_TIME_UTC;   // The time system
+    psString sys = pmConceptReadString(fpa, chip, cell, db, "CELL.TIMESYS"); // The time system as a string
+    if (! sys || strlen(sys) <= 0) {
+        psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
+    } else if (strcasecmp(sys, "TAI") == 0) {
+        timeSys = PS_TIME_TAI;
+    } else if (strcasecmp(sys, "UTC") == 0) {
+        timeSys = PS_TIME_UTC;
+    } else if (strcasecmp(sys, "UT1") == 0) {
+        timeSys = PS_TIME_UT1;
+    } else if (strcasecmp(sys, "TT") == 0) {
+        timeSys = PS_TIME_TT;
+    } else {
+        psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
+    }
+
+    psFree(sys);
+    return psMetadataItemAllocS32("CELL.TIMESYS", "Time system", timeSys);
+}
+
+
+psMetadataItem *pmConceptRead_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    // Need CELL.TIMESYS first
+    bool mdok = false;                  // Result of MD lookup
+    psTimeType timeSys = psMetadataLookupS32(&mdok, cell->concepts, "CELL.TIMESYS"); // The time system
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find CELL.TIMESYS in concepts --- assuming UTC.\n");
+        timeSys = PS_TIME_UTC;
+    }
+    psTime *time = NULL;                // The time
+
+    psMetadataItem *timeItem = pmConceptRead(fpa, chip, cell, db, "CELL.TIME");
+    if (! timeItem) {
+        psError(PS_ERR_IO, false, "Couldn't find CELL.TIME.\n");
+    } else {
+        // Get format
+        const psMetadata *camera = fpa->camera; // The camera configuration data
+        bool mdok = true;               // Status of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats) {
+            psString timeFormat = psMetadataLookupStr(&mdok, formats, "CELL.TIME");
+            if (mdok && strlen(timeFormat) > 0) {
+                switch (timeItem->type) {
+                case PS_DATA_STRING: {
+                        psString timeString = timeItem->data.V;   // String with the time
+                        if (strcasecmp(timeFormat, "ISO") == 0) {
+                            // timeString contains an ISO time
+                            time = psTimeFromISO(timeString, timeSys);
+                        } else if (strcasecmp(timeFormat, "JD") == 0) {
+                            double timeValue = strtod (timeString, NULL);
+                            time = psTimeFromJD(timeValue);
+                        } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                            double timeValue = strtod (timeString, NULL);
+                            time = psTimeFromMJD(timeValue);
+                        } else if (strstr(timeFormat, "SEPARATE")) {
+                            // timeString contains headers for the date and time
+                            psMetadata *header = NULL; // The FITS header
+                            if (cell->hdu) {
+                                header = cell->hdu->header;
+                            } else if (chip->hdu) {
+                                header = chip->hdu->header;
+                            } else if (fpa->hdu) {
+                                header = fpa->hdu->header;
+                            }
+                            if (! header) {
+                                psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                            } else {
+                                // Get the headers
+                                char *stuff1 = strpbrk(timeString, " ,;");
+                                psString dateName = psStringNCopy(timeString,
+                                                                  strlen(timeString) - strlen(stuff1));
+                                char *stuff2 = strpbrk(stuff1, "abcdefghijklmnopqrstuvwxyz"
+                                                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+                                psString timeName = psStringCopy(stuff2);
+
+                                bool mdok = true; // Status of MD lookup
+                                psString dateString = psMetadataLookupStr(&mdok, header, dateName);
+                                psFree(dateName);
+                                int day = 0, month = 0, year = 0;
+                                if (sscanf(dateString, "%d-%d-%d", &day, &month, &year) != 3 &&
+                                        sscanf(dateString, "%d/%d/%d", &day, &month, &year) != 3) {
+                                    psError(PS_ERR_IO, true, "Unable to read date: %s\n", dateString);
+                                } else {
+                                    if (strstr(timeFormat, "BACKWARDS")) {
+                                        int temp = day;
+                                        day = year;
+                                        year = temp;
+                                    }
+                                    if (strstr(timeFormat, "PRE2000") || year < 2000) {
+                                        year += 2000;
+                                    }
+
+                                    psMetadataItem *timeItem = psMetadataLookup(header, timeName);
+                                    if (! timeItem) {
+                                        psError(PS_ERR_IO, false, "Unable to find time header: %s\n",
+                                                timeName);
+                                    } else if (timeItem->type == PS_DATA_STRING) {
+                                        // Time is a string, in the usual way:
+                                        psStringAppend(&dateString, "T%s", timeItem->data.V);
+                                    } else {
+                                        // Assume that time is specified in Second of Day
+                                        double seconds = NAN;
+                                        switch (timeItem->type) {
+                                        case PS_TYPE_S32:
+                                            seconds = timeItem->data.S32;
+                                            break;
+                                        case PS_TYPE_F32:
+                                            seconds = timeItem->data.F32;
+                                            break;
+                                        case PS_TYPE_F64:
+                                            seconds = timeItem->data.F64;
+                                            break;
+                                        default:
+                                            psError(PS_ERR_IO, true, "Time header (%s) is not of an "
+                                                    "expected type: %x\n", timeName, timeItem->type);
+                                        }
+                                        // Now print to timeString as "hh:mm:ss.ss"
+                                        int hours = seconds / 3600;
+                                        seconds -= (double)hours * 3600.0;
+                                        int minutes = seconds / 60;
+                                        seconds -= (double)minutes * 60.0;
+                                        psStringAppend(&dateString, "T%02d:%02d:%02f", hours, minutes,
+                                                       seconds);
+                                    }
+                                    time = psTimeFromISO(dateString, timeSys);
+                                } // Reading date and time
+                                psFree(timeName);
+                            } // Reading headers
+                        } else {
+                            psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%s) --- trying "
+                                    "ISO\n", timeString);
+                            time = psTimeFromISO(timeString, timeSys);
+                        } // Interpreting the time string
+                    }
+                    break;
+                case PS_TYPE_F32: {
+                        double timeValue = (double)timeItem->data.F32;
+                        if (strcasecmp(timeFormat, "JD") == 0) {
+                            time = psTimeFromJD(timeValue);
+                        } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                            time = psTimeFromMJD(timeValue);
+                        } else {
+                            psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                                    "JD\n", timeValue);
+                            time = psTimeFromJD(timeValue);
+                        }
+                    }
+                    break;
+                case PS_TYPE_F64: {
+                        double timeValue = (double)timeItem->data.F64;
+                        if (strcasecmp(timeFormat, "JD") == 0) {
+                            time = psTimeFromJD(timeValue);
+                        } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                            time = psTimeFromMJD(timeValue);
+                        } else {
+                            psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                                    "JD\n", timeValue);
+                            time = psTimeFromJD(timeValue);
+                        }
+                    }
+                    break;
+                default:
+                    psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
+            } // Getting the format
+        } else {
+            psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
+        } // Getting the formats
+    } // Getting CELL.TIME
+
+    psMetadataItem *item = psMetadataItemAllocPtr("CELL.TIME", PS_DATA_TIME, "Time of exposure", time);
+    psFree(time);
+    return item;
+}
+
+// Correct a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
+static int fortranCorr(pmFPA *fpa,       // FPA, contains the camera configuration
+                       const char *name // Name of concept to check for FORTRAN indexing
+                      )
+{
+    bool mdok = false;                  // Result of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, fpa->camera, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, name);
+        if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+            return -1;
+        }
+    }
+    return 0;
+}
+
+psMetadataItem *pmConceptRead_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    int x0 = pmConceptReadS32(fpa, chip, cell, db, "CELL.X0");
+    x0 += fortranCorr(fpa, "CELL.X0");
+    return psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip", x0);
+}
+
+
+psMetadataItem *pmConceptRead_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    int y0 = pmConceptReadS32(fpa, chip, cell, db, "CELL.Y0");
+    y0 += fortranCorr(fpa, "CELL.X0");
+    return psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip", y0);
+}
+
+
+bool pmConceptWrite_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC");
+    psRegion *trimsec = trimsecItem->data.V; // The trimsec region
+    psString source = pmConceptReadString(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE"); // The source string
+
+    if (strcasecmp(source, "VALUE") == 0) {
+        // Check that it's the same value as stored in the camera
+        psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.TRIMSEC");
+        psRegion checkRegion = psRegionFromString(checkString);
+        if (! COMPARE_REGIONS(&checkRegion, trimsec)) {
+            psError(PS_ERR_IO, true, "Target CELL.TRIMSEC is specified by value, and values don't "
+                    "match.\n");
+            return false;
+        }
+        return true;
+    }
+
+    if (strcasecmp(source, "HEADER") == 0) {
+        psMetadata *header = NULL; // The FITS header
+        if (cell->hdu) {
+            header = cell->hdu->header;
+        } else if (chip->hdu) {
+            header = chip->hdu->header;
+        } else if (fpa->hdu) {
+            header = fpa->hdu->header;
+        }
+        if (! header) {
+            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+            return false;
+        }
+        psMetadataAddItem(header, trimsecItem, PS_LIST_TAIL, PS_META_REPLACE);
+        return true;
+    }
+
+    psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
+    return false;
+}
+
+bool pmConceptWrite_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
+    psList *biassecs = biassecItem->data.V; // The biassecs region list
+
+    if (biassecs->n == 0) {
+        psMetadataItem *noneItem = psMetadataItemAllocStr("CELL.BIASSEC", "Bias section", "NONE");
+        pmConceptWriteItem(fpa, chip, cell, db, noneItem);
+        return true;
+    }
+
+    psString source = pmConceptReadString(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE"); // The source string
+
+    if (strcasecmp(source, "VALUE") == 0) {
+        // Check that it's the same value as stored in the camera
+        psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
+        psList *checkList = psStringSplit(checkString, " ;");
+        if (biassecs->n != checkList->n) {
+            psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but number of "
+                    "entries doesn't match.\n");
+            psFree(checkList);
+            return false;
+        }
+
+        // We don't care if the order matches or not
+        psVector *check = psVectorAlloc(biassecs->n, PS_TYPE_U8); // Vector to mark regions off
+        for (int i = 0; i < check->n; i++) {
+            check->data.U8[i] = 0;
+        }
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psListIterator *checkListIter = psListIteratorAlloc(checkList, PS_LIST_HEAD, false);
+        psRegion *biassec = NULL; // Region from iteration
+        while ((biassec = psListGetAndIncrement(biassecsIter))) {
+            psListIteratorSet(checkListIter, PS_LIST_HEAD);
+            psString checkRegionString = NULL; // Region string from iteration
+            int i = 0;              // Counter
+            while ((checkRegionString = psListGetAndIncrement(checkListIter))) {
+                psRegion checkRegion = psRegionFromString(checkRegionString);
+                psTrace(__func__, 7, "Checking [%.0f:%.0f,%.0f:%.0f] against "
+                        "[%.0f:%.0f,%.0f:%.0f]\n", biassec->x0, biassec->x1, biassec->y0,
+                        biassec->y1, checkRegion.x0, checkRegion.x1, checkRegion.y0,
+                        checkRegion.y1);
+                if (COMPARE_REGIONS(biassec, &checkRegion)) {
+                    check->data.U8[i] = 1;
+                    i++;
+                    break;
+                }
+                i++;
+            }
+        }
+
+        bool allMatch = true;           // Does everything match?
+        for (int i = 0; i < check->n; i++) {
+            if (check->data.U8[i] == 0) {
+                psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but values "
+                        "don't match.\n");
+                allMatch = false;
+            }
+        }
+        // Clean up
+        psFree(checkListIter);
+        psFree(checkList);
+        psFree(biassecsIter);
+        psFree(check);
+
+        return allMatch;
+    }
+
+    if (strcasecmp(source, "HEADER") == 0) {
+        psString keywordsString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
+        psList *keywords = psStringSplit(keywordsString, " ,;");
+        if (biassecs->n != keywords->n) {
+            psError(PS_ERR_IO, true, "Target CELL.BIASSEC is sepcified by headers, but the number "
+                    "of headers doesn't match.\n");
+            psFree(keywords);
+            return false;
+        }
+
+        psMetadata *header = NULL; // The FITS header
+        if (cell->hdu) {
+            header = cell->hdu->header;
+        } else if (chip->hdu) {
+            header = chip->hdu->header;
+        } else if (fpa->hdu) {
+            header = fpa->hdu->header;
+        }
+        if (! header) {
+            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+            psFree(keywords);
+            return false;
+        }
+
+        psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false);
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false);
+        psString keyword = NULL; // Header keyword from list
+        while ((keyword = psListGetAndIncrement(keywordsIter))) {
+            // Update the header
+            psRegion *biassec = psListGetAndIncrement(biassecsIter);
+            psString biassecString = psRegionToString(*biassec);
+            psMetadataAdd(header, PS_LIST_TAIL, keyword, PS_DATA_STRING | PS_META_REPLACE, "Bias section",
+                          biassecString);
+            psFree(biassecString);
+        }
+        psFree(keywordsIter);
+        psFree(biassecsIter);
+        psFree(keywords);
+
+        return true;
+    }
+
+    psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
+    return false;
+}
+
+// This function actually does both CELL.XBIN and CELL.YBIN, since if CELL.XBIN and CELL.YBIN are specified by
+// the same header, we need to check to update them together.
+bool pmConceptWrite_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *xBinItem = psMetadataLookup(cell->concepts, "CELL.XBIN"); // Binning factor in x
+    psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y
+
+    const psMetadata *camera = fpa->camera;
+    psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
+    psString xKeyword = psMetadataLookupStr(NULL, translation, "CELL.XBIN");
+    psString yKeyword = psMetadataLookupStr(NULL, translation, "CELL.YBIN");
+    if (strlen(xKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0) {
+        pmConceptWriteToHeader(fpa, chip, cell, xBinItem);
+        xBinItem = NULL;
+    }
+    if (strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0) {
+        pmConceptWriteToHeader(fpa, chip, cell, yBinItem);
+        yBinItem = NULL;
+    }
+    if (strlen(xKeyword) > 0 && strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) == 0) {
+        psString binString = psStringCopy("");
+        psStringAppend(&binString, "%d,%d", xBinItem->data.S32, yBinItem->data.S32);
+        psMetadataItem *binItem = psMetadataItemAllocStr(xKeyword, "Binning factor in x and y", binString);
+        psFree(binString);
+        psMetadata *header = NULL; // The FITS header
+        if (cell->hdu) {
+            header = cell->hdu->header;
+        } else if (chip->hdu) {
+            header = chip->hdu->header;
+        } else if (fpa->hdu) {
+            header = fpa->hdu->header;
+        }
+        if (! header) {
+            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+            return false;
+        }
+        psMetadataAddItem(header, binItem, PS_LIST_TAIL, PS_META_REPLACE);
+        xBinItem = NULL;
+        yBinItem = NULL;
+        psFree(binItem);
+    }
+
+    // Do it in the usual way if we have to
+    if (xBinItem) {
+        pmConceptWriteItem(fpa, chip, cell, db, xBinItem);
+    }
+    if (yBinItem) {
+        pmConceptWriteItem(fpa, chip, cell, db, yBinItem);
+    }
+
+    return true;
+}
+
+// This is a dummy function, since CELL.YBIN is done by CELL.XBIN
+bool pmConceptWrite_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    return true;
+}
+
+
+
+bool pmConceptWrite_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *sysItem = psMetadataLookup(cell->concepts, "CELL.TIMESYS");
+    psString sys = NULL;            // String to store
+    switch (sysItem->data.S32) {
+    case PS_TIME_TAI:
+        sys = psStringCopy("TAI");
+        break;
+    case PS_TIME_UTC:
+        sys = psStringCopy("UTC");
+        break;
+    case PS_TIME_UT1:
+        sys = psStringCopy("UT1");
+        break;
+    case PS_TIME_TT:
+        sys = psStringCopy("TT");
+        break;
+    default:
+        sys = psStringCopy("Unknown");
+    }
+    psMetadataItem *newItem = psMetadataItemAllocStr("CELL.TIMESYS", "Time system", sys);
+    bool success = pmConceptWriteItem(fpa, chip, cell, db, newItem);
+    psFree(newItem);
+    psFree(sys);
+
+    return success;
+}
+
+bool pmConceptWrite_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *timeItem = psMetadataLookup(cell->concepts, "CELL.TIME");
+    psTime *time = timeItem->data.V; // The time
+    psString dateTimeString = psTimeToISO(time); // String representation
+
+    psMetadata *formats = psMetadataLookupMD(NULL, fpa->camera, "FORMATS");
+    psString format = psMetadataLookupStr(NULL, formats, "CELL.TIME");
+
+    if (strlen(format) == 0) {
+        // No format specified --> do it in the usual way (maybe it's a DB lookup)
+        psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                      dateTimeString);
+        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
+        psFree(newTimeItem);
+        psFree(dateTimeString);
+        return success;
+    }
+    if (strcasecmp(format, "ISO") == 0) {
+        // dateTimeString contains an ISO time
+        psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                      dateTimeString);
+        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
+        psFree(newTimeItem);
+        psFree(dateTimeString);
+        return success;
+    }
+    if (strstr(format, "SEPARATE")) {
+        // We're working with two separate headers
+        psMetadata *header = NULL; // The FITS header
+        if (cell->hdu) {
+            header = cell->hdu->header;
+        } else if (chip->hdu) {
+            header = chip->hdu->header;
+        } else if (fpa->hdu) {
+            header = fpa->hdu->header;
+        }
+        if (! header) {
+            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+            psFree(dateTimeString);
+            return false;
+        }
+
+        // Get the headers
+        const psMetadata *camera = fpa->camera;
+        psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
+        psString keywords = psMetadataLookupStr(NULL, translation, "CELL.TIME");
+        psList *dateTimeKeywords = psStringSplit(keywords, " ,;");
+        psString dateKeyword = psListGet(dateTimeKeywords, PS_LIST_HEAD);
+        psString timeKeyword = psListGet(dateTimeKeywords, PS_LIST_TAIL);
+
+        psList *dateTime = psStringSplit(dateTimeString, " T"); // Find the middle T
+        psString dateString = psListGet(dateTime, PS_LIST_HEAD);
+        psString timeString = psListGet(dateTime, PS_LIST_TAIL);
+
+        // XXX: Couldn't be bothered doing these right now
+        if (strstr(format, "PRE2000")) {
+            psError(PS_ERR_IO, true, "Don't you realise it's the twenty-first century?\n");
+            psFree(dateTimeString);
+            // Should free other stuff, but this is work in progress
+            return false;
+        }
+        if (strstr(format, "BACKWARDS")) {
+            psError(PS_ERR_IO, true, "You want it BACKWARDS?  Not right now, thanks.\n");
+            psFree(dateTimeString);
+            // Should free other stuff, but this is work in progress
+            return false;
+        }
+
+        bool success = true;
+        psMetadataItem *dateItem = psMetadataItemAllocStr(dateKeyword, "Date of observation", dateString);
+        psMetadataItem *timeItem = psMetadataItemAllocStr(timeKeyword, "Time of observation", timeString);
+        success = psMetadataAddItem(header, dateItem, PS_LIST_TAIL, PS_META_REPLACE) &&
+                  psMetadataAddItem(header, timeItem, PS_LIST_TAIL, PS_META_REPLACE);
+
+        psFree(dateTimeKeywords);
+        psFree(dateTime);
+        psFree(dateItem);
+        psFree(timeItem);
+
+        return success;
+    }
+    if (strcasecmp(format, "MJD") == 0) {
+        double mjd = psTimeToMJD(time);
+        psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "MJD of observation", mjd);
+        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
+        psFree(newTimeItem);
+        psFree(dateTimeString);
+        return success;
+    }
+    if (strcasecmp(format, "JD") == 0) {
+        double jd = psTimeToMJD(time);
+        psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
+        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
+        psFree(newTimeItem);
+        psFree(dateTimeString);
+        return success;
+    }
+
+    psError(PS_ERR_IO, true, "Not sure how to write concept CELL.TIME (%s)\n", dateTimeString);
+    return false;
+}
+
+bool pmConceptWrite_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *x0item = psMetadataLookup(cell->concepts, "CELL.X0");
+    psMetadataItem *newItem = psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip",
+                              x0item->data.S32 - fortranCorr(fpa, "CELL.X0"));
+    return pmConceptWriteItem(fpa, chip, cell, db, newItem);
+}
+
+bool pmConceptWrite_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *y0item = psMetadataLookup(cell->concepts, "CELL.Y0");
+    psMetadataItem *newItem = psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip",
+                              y0item->data.S32 - fortranCorr(fpa, "CELL.Y0"));
+    return pmConceptWriteItem(fpa, chip, cell, db, newItem);
+}
+
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.h	(revision 6448)
@@ -0,0 +1,28 @@
+#ifndef PM_CONCEPTS_STANDARD_H
+#define PM_CONCEPTS_STANDARD_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+psMetadataItem *pmConceptRead_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptRead_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+bool pmConceptWrite_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.c	(revision 6448)
@@ -0,0 +1,377 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmConceptsRead.h"
+#include "psAdditionals.h"
+
+
+static bool compareConcepts(psMetadataItem *item1, // First item to compare
+                            psMetadataItem *item2 // Second item to compare
+                           )
+{
+    // First order checks
+    if (! item1 || ! item2) {
+        return false;
+    }
+    if (strcasecmp(item1->name, item2->name) != 0) {
+        return false;
+    }
+
+    // Check the more boring types
+    switch (item1->type) {
+    case PS_TYPE_S32:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.S32 == item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.S32 == (int)item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.S32 == (int)item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+    case PS_TYPE_F32:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.F32 == (float)item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.F32 == item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.F32 == (float)item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+    case PS_TYPE_F64:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.F64 == (double)item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.F64 == (double)item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.F64 == item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+        return (item1->data.F64 == item2->data.F64) ? true : false;
+    case PS_DATA_STRING:
+        if (item2->type != PS_DATA_STRING) {
+            return false;
+        }
+        return (strcasecmp(item1->data.V, item2->data.V) == 0) ? true : false;
+    default:
+        return false;
+    }
+    psAbort(__func__, "Should never get here.\n");
+}
+
+
+// Well, not really "write", but check to make sure it's there and matches
+bool pmConceptWriteToCamera(pmCell *cell, // The cell
+                            psMetadataItem *concept // Concept
+                           )
+{
+    if (! cell->camera) {
+        return false;
+    }
+    if (cell) {
+        psMetadataItem *item = psMetadataLookup(cell->camera, concept->name); // Info we want
+        return compareConcepts(item, concept);
+    }
+
+    return false;
+}
+
+// Write the concept to the header in the appropriate location
+bool pmConceptWriteToHeader(pmFPA *fpa, // The FPA that contains the chip
+                            pmChip *chip, // The chip that contains the cell
+                            pmCell *cell, // The cell
+                            psMetadataItem *concept // Concept
+                           )
+{
+    bool mdok = true;                   // Status of MD lookup
+    bool status = false;                // Status of setting header
+    if (! fpa->camera) {
+        return false;
+    }
+    psMetadata *translation = psMetadataLookupMD(&mdok, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdok) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return false;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdok, translation, concept->name);
+    if (mdok && strlen(keyword) > 0) {
+        psTrace(__func__, 6, "It's in keyword %s\n", keyword);
+        psMetadataItem *headerItem = NULL; // Item to add to header
+        // XXX: Need to expand range of types
+        switch (concept->type) {
+        case PS_DATA_STRING:
+            headerItem = psMetadataItemAllocStr(keyword, concept->comment, concept->data.V);
+            break;
+        case PS_DATA_S32:
+            headerItem = psMetadataItemAllocS32(keyword, concept->comment, concept->data.S32);
+            break;
+        case PS_DATA_F32:
+            headerItem = psMetadataItemAllocF32(keyword, concept->comment, concept->data.F32);
+            break;
+        case PS_DATA_F64:
+            headerItem = psMetadataItemAllocF64(keyword, concept->comment, concept->data.F64);
+            break;
+        default:
+            headerItem = psMetadataItemAlloc(keyword, concept->type, concept->comment,
+                                             concept->data.V); // Item for the header
+        }
+
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psTrace(__func__, 7, "Adding to the cell level header...\n");
+            psMetadataAddItem(cell->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (chip && chip->hdu) {
+            psTrace(__func__, 7, "Adding to the chip level header...\n");
+            psMetadataAddItem(chip->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (fpa->hdu) {
+            psTrace(__func__, 7, "Adding to the FPA level header...\n");
+            psMetadataAddItem(fpa->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else {
+            // In desperation, add to the PHU --- it HAS to be in the header somewhere!
+            if (! fpa->phu) {
+                fpa->phu = psMetadataAlloc();
+            }
+            psTrace(__func__, 7, "Adding to the PHU...\n");
+            psMetadataAddItem(fpa->phu, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        }
+        psFree(headerItem);
+    }
+
+    // No header value
+    return status;
+}
+
+
+// Well, not really "write", but check to see if it's there, and matches
+bool pmConceptWriteToDefault(pmFPA *fpa, // The FPA that contains the chip
+                             pmChip *chip, // The chip that contains the cell
+                             pmCell *cell, // The cell
+                             psMetadataItem *concept // Concept
+                            )
+{
+    bool mdOK = true;                   // Status of MD lookup
+    if (! fpa->camera) {
+        return false;
+    }
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK || ! defaults) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return false;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept->name);
+    bool status = false;                // Result of checking the database
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept->name);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return false;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = pmConceptReadFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept->name);
+                return false;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V); // This is now what we were after
+        }
+
+        status = compareConcepts(defItem, concept);
+        if (! status) {
+            psError(PS_ERR_IO, true, "Concept %s is specified by default in the camera configuration, "
+                    "but doesn't match the actual value.\n", concept->name);
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return status;
+}
+
+
+// XXX: Not tested at all
+// XXX I WOULD NOT TRUST THIS FUNCTION IN THE SLIGHTEST YET! --- PAP
+bool pmConceptWriteToDB(pmFPA *fpa, // The FPA that contains the chip
+                        pmChip *chip, // The chip that contains the cell
+                        pmCell *cell, // The cell
+                        psDB *db,    // DB handle
+                        psMetadataItem *concept // Concept
+                       )
+{
+    if (! db) {
+        // No database initialised
+        return false;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    if (! fpa->camera) {
+        return false;
+    }
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept->name);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        //        const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // List of value names for the columns
+            psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
+            if (cols->n != values->n) {
+                psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
+                         "the same number of entries --- ignored.\n", concept);
+            } else {
+                // Iterators for the lists
+                psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
+                psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+                char *column = NULL;    // Name of the column
+                while ((column = psListGetAndIncrement(colsIter))) {
+                    char *name = psListGetAndIncrement(valuesIter); // Name for the value
+                    if (!strlen(column) || !strlen(name)) {
+                        psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
+                                 " empty --- ignored.\n", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = pmConceptReadFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = pmConceptReadFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } else {
+                            // We need to create a new psMetadataItem.  I don't think we can't simply hack
+                            // the existing one, since that could conceivably cause memory leaks
+                            psMetadataItem *newItem = psMetadataItemAlloc(concept->name, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    psFree(column);
+                } // Iterating through the columns
+                psFree(colsIter);
+                psFree(valuesIter);
+
+                // Check first to make sure we're only going to touch one row
+                psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
+                // Note that we use limit=2 in order to test if there are multiple rows returned
+                if (! dbResult || dbResult->n == 0) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n",
+                             concept->name);
+                    return false;
+                } else {
+                    if (dbResult->n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " ignored.\n", concept->name);
+                    }
+                    // Update the DB
+                    psMetadata *update = psMetadataAlloc();
+                    psMetadataAddItem(update, concept, PS_LIST_HEAD, 0);
+                    psDBUpdateRows(db, tableName, selection, update);
+                    psFree(update);
+                    return true;
+                }
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here?\n");
+    return false;
+}
+
+
+// Concept write from item
+bool pmConceptWriteItem(pmFPA *fpa,     // The FPA
+                        pmChip *chip,   // The chip
+                        pmCell *cell,   // The cell
+                        psDB *db,       // DB handle
+                        psMetadataItem *concept // Concept item
+                       )
+{
+    if (! fpa->camera) {
+        return false;
+    }
+
+    // Try headers, database, defaults in order
+    psTrace(__func__, 3, "Trying to set concept %s...\n", concept->name);
+    bool status = pmConceptWriteToCamera(cell, concept); // Status for return
+    if (! status) {
+        psTrace(__func__, 5, "Trying header....\n");
+        status = pmConceptWriteToHeader(fpa, chip, cell, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Trying database....\n");
+        status = pmConceptWriteToDB(fpa, chip, cell, db, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Checking defaults....\n");
+        status = pmConceptWriteToDefault(fpa, chip, cell, concept);
+    }
+
+    if (! status) {
+        psError(PS_ERR_IO, true, "Unable to set %s (%s).\n", concept->name, concept->comment);
+    }
+
+    return status;
+}
+
+
+// Concept write
+bool pmConceptWrite(pmFPA *fpa, // The FPA
+                    pmChip *chip,// The chip
+                    pmCell *cell,    // The cell
+                    psDB *db, // DB handle
+                    psMetadata *concepts, // Concepts MD from which to set
+                    const char *name // Name of the concept
+                   )
+{
+    psMetadataItem *concept = psMetadataLookup(concepts, name);
+    if (! concept) {
+        psError(PS_ERR_IO, true, "No such concept as %s\n", name);
+        return false;
+    }
+    return pmConceptWriteItem(fpa, chip, cell, db, concept);
+}
+
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.h	(revision 6448)
@@ -0,0 +1,49 @@
+#ifndef PM_CONCEPTS_WRITE_H
+#define PM_CONCEPTS_WRITE_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Well, not really "write", but check to make sure it's there and matches
+bool pmConceptWriteToCamera(pmCell *cell, // The cell
+                            psMetadataItem *concept // Concept
+                           );
+
+// Write the concept to the header in the appropriate location
+bool pmConceptWriteToHeader(pmFPA *fpa, // The FPA that contains the chip
+                            pmChip *chip, // The chip that contains the cell
+                            pmCell *cell, // The cell
+                            psMetadataItem *concept // Concept
+                           );
+
+// Well, not really "write", but check to see if it's there, and matches
+bool pmConceptWriteToDefault(pmFPA *fpa, // The FPA that contains the chip
+                             pmChip *chip, // The chip that contains the cell
+                             pmCell *cell, // The cell
+                             psMetadataItem *concept // Concept
+                            );
+
+bool pmConceptWriteToDB(pmFPA *fpa, // The FPA that contains the chip
+                        pmChip *chip, // The chip that contains the cell
+                        pmCell *cell, // The cell
+                        psDB *db,    // DB handle
+                        psMetadataItem *concept // Concept
+                       );
+
+// Concept write from item
+bool pmConceptWriteItem(pmFPA *fpa,     // The FPA
+                        pmChip *chip,   // The chip
+                        pmCell *cell,   // The cell
+                        psDB *db,       // DB handle
+                        psMetadataItem *concept // Concept item
+                       );
+
+bool pmConceptWrite(pmFPA *fpa, // The FPA
+                    pmChip *chip,// The chip
+                    pmCell *cell,    // The cell
+                    psDB *db, // DB handle
+                    psMetadata *concepts, // Concepts MD from which to set
+                    const char *name // Name of the concept
+                   );
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPA.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPA.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPA.c	(revision 6448)
@@ -0,0 +1,684 @@
+/** @file  pmFPA.c
+*
+*  @brief This file defines the basic types for the FPA hierarchy
+*
+*  @ingroup AstroImage
+*
+*  @author GLG, MHPCC
+*
+* XXX: We should review the extent of the warning messages on these functions
+* when the transformations are not successful.
+*
+* XXX: Should we implement non-linear cell->chip transforms?
+*
+*  @version $Revision: 1.1.4.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-02-17 17:13:41 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmConcepts.h"
+#include "pmMaskBadPixels.h"
+
+/******************************************************************************
+ *****************************************************************************/
+#define PS_FREE_HIERARCHY 1
+#define PARENT_LINKS 0
+
+static void readoutFree(pmReadout *readout)
+{
+    if (readout != NULL) {
+        psTrace(__func__, 9, "Removing readout %lx from cell %lx...\n", (size_t) readout, (size_t) readout->parent);
+        if (readout->parent) {
+            psArray *readouts = readout->parent->readouts;
+            for (int i = 0; i < readouts->n; i++) {
+                if (readouts->data[i] == readout) {
+                    //                    pmReadout *tmpReadout = readouts->data[i];
+                    readouts->data[i] = NULL;
+                    #if PARENT_LINKS
+
+                    psFree(tmpReadout);
+                    #endif
+
+                    break;
+                }
+            }
+        }
+        psTrace(__func__, 9, "Freeing readout %lx\n", (size_t) readout);
+
+        #if PARENT_LINKS
+
+        psFree(readout->parent);
+        #endif
+
+        psFree(readout->image);
+        psFree(readout->mask);
+        psFree(readout->weight);
+        psFree(readout->analysis);
+        psFree(readout->bias);
+    }
+}
+
+static void cellFree(pmCell *cell)
+{
+    if (cell != NULL) {
+        psTrace(__func__, 9, "Removing cell %lx from chip %lx...\n", (size_t)cell, (size_t)cell->parent);
+        if (cell->parent) {
+            psArray *cells = cell->parent->cells;
+            for (int i = 0; i < cells->n; i++) {
+                if (cells->data[i] == cell) {
+                    //                    pmCell *tmpCell = cells->data[i];
+                    cells->data[i] = NULL;
+                    #if PARENT_LINKS
+
+                    psFree(tmpCell);
+                    #endif
+
+                    break;
+                }
+            }
+        }
+        psTrace(__func__, 9, "Freeing cell %lx\n", (size_t)cell);
+
+        pmCellFreeReadouts(cell);
+        psFree(cell->readouts);
+        #if PARENT_LINKS
+
+        psFree(cell->parent);
+        #endif
+
+        psFree(cell->toChip);
+        psFree(cell->toFPA);
+        psFree(cell->toSky);
+        psFree(cell->concepts);
+        psFree(cell->analysis);
+        psFree(cell->camera);
+        psFree(cell->hdu);
+    }
+}
+
+static void chipFree(pmChip* chip)
+{
+    if (chip != NULL) {
+        psTrace(__func__, 9, "Removing chip %lx from fpa %lx...\n", (size_t)chip, (size_t)chip->parent);
+        if (chip->parent) {
+            psArray *chips = chip->parent->chips;
+            for (int i = 0; i < chips->n; i++) {
+                if (chips->data[i] == chip) {
+                    //                    pmChip *tmpChip = chips->data[i];
+                    chips->data[i] = NULL;
+                    #if PARENT_LINKS
+
+                    psFree(tmpChip);
+                    #endif
+
+                    break;
+                }
+            }
+        }
+        psTrace(__func__, 9, "Freeing chip %lx\n", (size_t)chip);
+
+        pmChipFreeCells(chip);
+        psFree(chip->cells);
+        #if PARENT_LINKS
+
+        psFree(chip->parent);
+        #endif
+
+        psFree(chip->toFPA);
+        psFree(chip->fromFPA);
+        psFree(chip->concepts);
+        psFree(chip->analysis);
+        psFree(chip->hdu);
+    }
+}
+
+
+static void FPAFree(pmFPA *fpa)
+{
+    if (fpa != NULL) {
+        psTrace(__func__, 9, "Freeing fpa %lx\n", (size_t)fpa);
+        psFree(fpa->fromTangentPlane);
+        psFree(fpa->toTangentPlane);
+        psFree(fpa->projection);
+        psFree(fpa->concepts);
+        psFree(fpa->analysis);
+        psFree(fpa->camera);
+        //
+        // Set the parent to NULL in all fpa->chips before psFree(fpa->chips)
+        // in order to avoid memory reference counter problems.
+        //
+        psArray *chips = fpa->chips;
+        for (psS32 i = 0 ; i < chips->n ; i++) {
+            pmChip *tmpChip = chips->data[i];
+            if (! tmpChip) {
+                continue;
+            }
+            chips->data[i] = NULL;
+            tmpChip->parent = NULL;
+            if (PS_FREE_HIERARCHY == 1) {
+                psFree(tmpChip);
+            }
+        }
+        psFree(fpa->chips);
+        psFree(fpa->hdu);
+        psFree(fpa->phu);
+    }
+}
+
+void pmCellFreeReadouts(pmCell *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;
+        }
+        readouts->data[i] = NULL;
+        tmpReadout->parent = NULL;
+        psTrace(__func__, 9, "Will now free readout %lx...\n", (size_t)tmpReadout);
+        psFree(tmpReadout); // Drop the readout
+        #if PARENT_LINKS
+
+        psFree(cell); // Decrement reference counter on cell, since readout->parent isn't held any more
+        #endif
+
+    }
+    cell->readouts = psArrayRealloc(cell->readouts, 0);
+    cell->readouts->n = 0;
+}
+
+
+void pmChipFreeCells(pmChip *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;
+        }
+        cells->data[i] = NULL;
+        tmpCell->parent = NULL;
+        pmCellFreeReadouts(tmpCell);// Drop all readouts the cell holds
+        psFree(tmpCell);            // Drop the cell
+        #if PARENT_LINKS
+
+        psFree(chip); // Decrement reference counter on chip, since cell->parent isn't held any more
+        #endif
+
+    }
+    chip->cells = psArrayRealloc(chip->cells, 0);
+    chip->cells->n = 0;
+}
+
+
+
+void p_pmHDUFree(p_pmHDU *hdu)
+{
+    if (hdu) {
+        psFree(hdu->extname);
+        psFree(hdu->header);
+        psFree(hdu->images);
+        psFree(hdu->masks);
+        psFree(hdu->weights);
+    }
+}
+
+// XXX: Verify these default values for row0, col0, rowBins, colBins
+// PAP: These values may disappear in the future in favour of values in parent->concepts?
+pmReadout *pmReadoutAlloc(pmCell *cell)
+{
+    pmReadout *tmpReadout = (pmReadout *) psAlloc(sizeof(pmReadout));
+
+    tmpReadout->col0 = 0;
+    tmpReadout->row0 = 0;
+    tmpReadout->colBins = 0;
+    tmpReadout->rowBins = 0;
+    tmpReadout->image = NULL;
+    tmpReadout->mask = NULL;
+    tmpReadout->weight = NULL;
+    tmpReadout->bias = psListAlloc(NULL);
+    tmpReadout->analysis = psMetadataAlloc();
+    #if PARENT_LINKS
+
+    tmpReadout->parent = psMemIncrRefCounter(cell);
+    #else
+
+    tmpReadout->parent = cell;
+    #endif
+
+    if (cell != NULL) {
+        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
+    }
+    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
+    return(tmpReadout);
+}
+
+// XXX: Verify these default values for row0, col0.
+// PAP: These values may disappear in the future in favour of values in the "concepts"?
+pmCell *pmCellAlloc(
+    pmChip *chip,
+    psMetadata *cameraData,
+    const char *name)
+{
+    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
+
+    tmpCell->col0 = 0;
+    tmpCell->row0 = 0;
+    tmpCell->toChip = NULL;
+    tmpCell->toFPA = NULL;
+    tmpCell->toSky = NULL;
+    tmpCell->concepts = psMetadataAlloc();
+    psBool rc = psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name);
+    if (rc == false) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
+    }
+    tmpCell->camera = psMemIncrRefCounter(cameraData);
+    tmpCell->analysis = psMetadataAlloc();
+    tmpCell->readouts = psArrayAlloc(0);
+    #if PARENT_LINKS
+
+    tmpCell->parent = psMemIncrRefCounter(chip);
+    #else
+
+    tmpCell->parent = chip;
+    #endif
+
+    if (chip != NULL) {
+        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
+    }
+    tmpCell->process = true;            // All cells are processed by default
+    tmpCell->exists = false;            // Not yet read in
+    tmpCell->hdu = NULL;
+
+    pmConceptsBlankCell(tmpCell);
+
+    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
+    return(tmpCell);
+}
+
+// XXX: Verify these default values for row0, col0.
+// PAP: row0, col0 may disappear in the future in favour of storing values in the "concepts".
+pmChip *pmChipAlloc(
+    pmFPA *fpa,
+    const char *name)
+{
+    pmChip *tmpChip = (pmChip *) psAlloc(sizeof(pmChip));
+
+    tmpChip->col0 = 0;
+    tmpChip->row0 = 0;
+    tmpChip->toFPA = NULL;
+    tmpChip->fromFPA = NULL;
+    tmpChip->concepts = psMetadataAlloc();
+    psBool rc = psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name);
+    if (rc == false) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME to metadata.\n");
+    }
+    tmpChip->analysis = psMetadataAlloc();
+    tmpChip->cells = psArrayAlloc(0);
+    #if PARENT_LINKS
+
+    tmpChip->parent = psMemIncrRefCounter(fpa);
+    #else
+
+    tmpChip->parent = fpa;
+    #endif
+
+    if (fpa != NULL) {
+        fpa->chips = psArrayAdd(fpa->chips, 1, (psPtr) tmpChip);
+    }
+    tmpChip->process = true;            // Work on all chips, by default
+    tmpChip->exists = false;            // Not read in yet
+    tmpChip->hdu = NULL;
+
+    pmConceptsBlankChip(tmpChip);
+
+    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
+    return(tmpChip);
+}
+
+pmFPA *pmFPAAlloc(const psMetadata *camera)
+{
+    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));
+
+    tmpFPA->fromTangentPlane = NULL;
+    tmpFPA->toTangentPlane = NULL;
+    tmpFPA->projection = NULL;
+    tmpFPA->concepts = psMetadataAlloc();
+    tmpFPA->analysis = NULL;
+    tmpFPA->camera = psMemIncrRefCounter((psPtr)camera);
+    tmpFPA->chips = psArrayAlloc(0);
+    tmpFPA->hdu = NULL;
+    tmpFPA->phu = NULL;
+
+    pmConceptsBlankFPA(tmpFPA);
+
+    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
+    return(tmpFPA);
+}
+
+p_pmHDU *p_pmHDUAlloc(const char *extname)
+{
+    p_pmHDU *hdu = psAlloc(sizeof(p_pmHDU));
+    psMemSetDeallocator(hdu, (psFreeFunc)p_pmHDUFree);
+
+    hdu->extname = psStringCopy(extname);
+    hdu->header = NULL;
+    hdu->images = NULL;
+    hdu->masks = NULL;
+    hdu->weights = NULL;
+
+    return hdu;
+}
+
+static psBool cellCheckParents(pmCell *cell)
+{
+    if (cell == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
+        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpReadout, false);
+        if (tmpReadout->parent != cell) {
+            tmpReadout->parent = cell;
+            flag = false;
+        }
+    }
+    return(flag);
+}
+
+static psBool chipCheckParents(pmChip *chip)
+{
+    if (chip == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < chip->cells->n ; i++) {
+        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpCell, false);
+        if (tmpCell->parent != chip) {
+            tmpCell->parent = chip;
+            flag = false;
+        }
+
+        flag &= cellCheckParents(tmpCell);
+    }
+    return(flag);
+}
+
+psBool pmFPACheckParents(pmFPA *fpa)
+{
+    if (fpa == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpChip, false);
+        if (tmpChip->parent != fpa) {
+            tmpChip->parent = fpa;
+            flag = false;
+        }
+
+        flag &= chipCheckParents(tmpChip);
+    }
+    return(flag);
+}
+
+
+
+/*****************************************************************************
+ *****************************************************************************/
+
+// 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)
+{
+    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 {
+            tmpChip->process = false;
+            setCellsProcess(tmpChip, false);
+        }
+
+    }
+
+    return true;
+}
+
+bool pmChipSelectCell(pmChip *chip,
+                      int cellNum
+                     )
+{
+    assert(chip);
+
+    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;
+        }
+        cell->process = (i == cellNum);
+    }
+
+    return true;
+}
+
+/*****************************************************************************
+XXX: The SDRS is ambiguous on a few things:
+    Whether or not the other chips should be set process=true. [PAP: No]
+    Should we return the number of chip process=true before or after they're set, [PAP: After]
+ *****************************************************************************/
+/**
+ *
+ * pmFPAExcludeChip shall set process to false only for the specified chip
+ * number (chipNum). In the event that the specified chip number does not exist
+ * within the fpa, the function shall generate a warning, and perform no action.
+ * The function shall return the number of chips within the fpa that have process
+ * set to true.
+ *
+ */
+int pmFPAExcludeChip(
+    pmFPA *fpa,
+    int chipNum)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    psArray *chips = fpa->chips;        // Component chips
+    if (chips == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: fpa->chips == NULL\n");
+        return(0);
+    }
+    if ((chipNum >= chips->n) || (NULL == (pmChip *) chips->data[chipNum])) {
+        psLogMsg(__func__, PS_LOG_WARN, "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
+                     )
+{
+    assert(chip);
+
+    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;
+}
+
+
+bool pmCellSetWeights(pmCell *cell // Cell for which to set weights
+                     )
+{
+    float gain = psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN"); // Cell gain
+    float readnoise = psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); // Trim section
+    float saturation = psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION"); // Saturation level
+    float bad = psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD"); // Bad level
+
+    p_pmHDU *hdu = cell->hdu;           // The data unit, containing the weight and mask originals
+    if (!hdu) {
+        pmChip *chip = cell->parent;    // The parent chip
+        if (chip->hdu) {
+            hdu = chip->hdu;
+        } else {
+            pmFPA *fpa = chip->parent;  // The parent FPA
+            hdu = fpa->hdu;
+            if (!hdu) {
+                psError(PS_ERR_UNKNOWN, true, "Unable to find the HDU in the hierarchy!\n");
+                return false;
+            }
+        }
+    }
+
+    psArray *pixels = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    psArray *masks = hdu->masks;        // Array of mask images
+    // Generate the weights and masks if required
+    if (! weights) {
+        weights = psArrayAlloc(pixels->n);
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+            psImageInit(weights->data[i], 0.0);
+        }
+        hdu->weights = weights;
+    }
+    if (! masks) {
+        masks = psArrayAlloc(pixels->n);
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+            psImageInit(masks->data[i], 0);
+        }
+        hdu->masks = masks;
+    }
+
+    // Set the pixels
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout of interest
+
+        if (! readout->weight) {
+            readout->weight = psMemIncrRefCounter(psImageSubset(weights->data[i], *trimsec));
+        }
+        if (! readout->mask) {
+            readout->mask = psMemIncrRefCounter(psImageSubset(masks->data[i], *trimsec));
+        }
+
+        // Set up the mask
+        psImage *image = readout->image;// Pixels
+        psImage *mask = readout->mask;  // Mask image
+        for (int i = 0; i < image->numRows; i++) {
+            for (int j = 0; j < image->numCols; j++) {
+                if (image->data.F32[i][j] > saturation) {
+                    mask->data.U8[i][j] = PM_MASK_SAT;
+                }
+                if (image->data.F32[i][j] < bad) {
+                    mask->data.U8[i][j] = PM_MASK_BAD;
+                }
+            }
+        }
+
+        // Set weight image to the variance = g*f + rn^2
+        psBinaryOp(readout->weight, image, "/", psScalarAlloc(gain, PS_TYPE_F32));
+        psBinaryOp(readout->weight, readout->weight, "+",
+                   psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+    }
+
+    return true;
+}
+
+
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPA.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPA.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPA.h	(revision 6448)
@@ -0,0 +1,294 @@
+/** @file  pmFPA.h
+*
+*  @brief This file defines the basic types the focal plane hierarchy.
+*
+*  @ingroup AstroImage
+*
+*  @author GLG, MHPCC
+*
+*  @version $Revision: 1.1.4.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-02-17 17:13:41 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#ifndef PM_FPA_H
+#define PM_FPA_H
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "pslib.h"
+#include "psDB.h"
+
+/// @addtogroup AstroImage
+/// @{
+
+// XXX: Is this correct?  Must determine what p_pmHDU is.
+// XXX: Create the p_pmHDU alloc/free.
+typedef struct
+{
+    const char *extname;                // Extension name, if it corresponds to this level
+    psMetadata *header;                 // The FITS header, if it corresponds to this level
+    psArray *images;                    // The pixel data, if it corresponds to this level
+    psArray *masks;                     // The mask data, if it corresponds to this level
+    psArray *weights;                   // The weight data, if it corresponds to this level
+}
+p_pmHDU;
+
+/** Focal plane data structure
+ *
+ *  A focal plane consists of one or more chips (according to the number of
+ *  pieces of contiguous silicon). It contains metadata containers for the
+ *  concepts and analysis, a link to the parent, and pointers to the FITS header,
+ *  if that corresponds to this level (the FPA may be the PHU, but will not ever
+ *  contain pixels). For astrometry, it contains a transformation from the focal
+ *  plane to the tangent plane and the fixed pattern residuals. It is expected
+ *  that the transformation will consist of two 4D polynomials (i.e. a function
+ *  of two coordinates in position, the magnitude of the object, and the color of
+ *  the object) in order to correct for optical distortions and the effects of
+ *  the atmosphere; hence we think that it is prudent to include a reverse
+ *  transformation which will be derived from numerically inverting the forward
+ *  transformation.
+ *
+ */
+typedef struct
+{
+    // Astrometric transformations
+    psPlaneDistort* fromTangentPlane;   ///< Transformation from tangent plane to focal plane
+    psPlaneDistort* toTangentPlane;     ///< Transformation from focal plane to tangent plane
+    psProjection *projection;           ///< Projection from tangent plane to sky
+    // Information
+    psMetadata *concepts;               ///< Cache for PS concepts
+    psMetadata *analysis;               ///< FPA-level analysis metadata
+    const psMetadata *camera;           ///< Camera configuration
+    psArray *chips;                     ///< The chips
+    p_pmHDU *hdu;                       ///< FITS data
+    psMetadata *phu;                    ///< Primary Header
+}
+pmFPA;
+
+/** Chip data structure
+ *
+ *  A chip consists of one or more cells (according to the number of amplifiers
+ *  on the device). The chip contains metadata containers for the concepts and
+ *  analysis, a link to the parent, and pointers to the pointers to the various
+ *  FITS data, if that corresponds to this level. For astrometry, in addition to
+ *  the rough positioning information, it contains a coordinate transform from
+ *  the chip to the focal plane. It is expected that this transform will consist
+ *  of two second-order 2D polynomials; hence we think that it is prudent to
+ *  include a reverse transformation which will be derived from numerically
+ *  inverting the forward transformation. A boolean indicates whether the chip is
+ *  of interest, allowing it to be excluded from analysis.
+ *
+ */
+typedef struct
+{
+    // Offset specifying position on focal plane
+    int col0;                           ///< Offset from the left of FPA.
+    int row0;                           ///< Offset from the bottom of FPA.
+    // Astrometric transformations
+    psPlaneTransform* toFPA;            ///< Transformation from chip to FPA coordinates
+    psPlaneTransform* fromFPA;          ///< Transformation from FPA to chip coordinates
+    // Information
+    psMetadata *concepts;               ///< Cache for PS concepts
+    psMetadata *analysis;               ///< Chip-level analysis metadata
+    psArray *cells;                     ///< The cells (referred to by name)
+    pmFPA *parent;                      ///< Parent FPA
+    bool process;                       ///< Do we bother about reading and working with this chip?
+    bool exists;                        ///< Does the chip exist --- has it been read in?
+    p_pmHDU *hdu;                       ///< FITS data
+}
+pmChip;
+
+/** 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
+{
+    // Offset specifying position on chip
+    int col0;                           ///< Offset from the left of chip.
+    int row0;                           ///< Offset from the bottom of chip.
+    // Astrometric transformations
+    psPlaneTransform* toChip;           ///< Transformations from cell to chip coordinates
+    psPlaneTransform* toFPA;            ///< Transformations from cell to FPA coordinates
+    psPlaneTransform* toSky;            ///< Transformations from cell to sky coordinates
+    // Information
+    psMetadata *concepts;               ///< Cache for PS concepts
+    psMetadata *camera;                 ///< Camera Info
+    psMetadata *analysis;               ///< Cell-level analysis metadata
+    psArray *readouts;                  ///< The readouts (referred to by number)
+    pmChip *parent;                     ///< Parent chip
+    bool process;                       ///< Do we bother about reading and working with this cell?
+    bool exists;                        ///< Does the cell exist --- has it been read in?
+    p_pmHDU *hdu;                       ///< FITS data
+}
+pmCell;
+
+/** Readout data structure.
+ *
+ *  A readout is the result of a single read of a cell (or a portion thereof).
+ *  It contains the offset from the lower-left corner of the chip, in the case
+ *  that the CCD was windowed, as well as the binning factors and parity (if the
+ *  binning value is negative, then the parity is reversed). It also contains the
+ *  pixel data, metadata containers for the concepts and analysis, and a link to
+ *  the parent.
+ *
+ */
+typedef struct
+{
+    // Position on the cell
+    // XXX: These may be removed in the future; use parent->concepts instead?
+    int col0;                           ///< Offset from the left of chip.
+    int row0;                           ///< Offset from the bottom of chip.
+    int colBins;                        ///< Amount of binning in x-dimension
+    int rowBins;                        ///< Amount of binning in y-dimension
+    // Information
+    psImage *image;                     ///< Imaging area of readout
+    psImage *mask;                      ///< Mask of input image
+    psImage *weight;                    ///< Weight of input image
+    psList *bias;                       ///< Overscan images
+    psMetadata *analysis;               ///< Readout-level analysis metadata
+    pmCell *parent;                     ///< Parent cell
+}
+pmReadout;
+
+void pmCellFreeReadouts(pmCell *cell);
+void pmChipFreeCells(pmChip *chip);
+
+/** Allocates a pmReadout
+ *
+ *  The constructor shall make an empty pmReadout. If the parent cell is not
+ *  NULL, the parent link is made and the readout shall be placed in the
+ *  parents array of readouts. The metadata containers shall be allocated. All
+ *  other pointers in the structure shall be initialized to NULL.
+ *
+ *  @return pmReadout*    newly allocated pmReadout with all internal pointers set to NULL
+ */
+pmReadout *pmReadoutAlloc(
+    pmCell *cell                        ///< Parent cell
+);
+
+/** Allocates a pmCell
+ *
+ *  The constructor shall make an empty pmCell. If the parent chip is not NULL,
+ *  the parent link is made and the cell shall be placed in the parents array of
+ *  cells. The readouts array shall be allocated with a zero size, and the
+ *  metadata containers constructed. All other pointers in the structure shall be
+ *  initialized to NULL.
+ *
+ *  @return pmCell*    newly allocated pmCell
+ */
+pmCell *pmCellAlloc(
+    pmChip *chip,       ///< Parent chip
+    psMetadata *cameraData, ///< Camera data
+    const char *name    ///< Name of cell
+);
+
+/** Allocates a pmChip
+ *
+ *  The constructor shall make an empty pmChip. If the parent fpa is not NULL,
+ *  the parent link is made and the chip shall be placed in the parent's array
+ *  of chips. The cells array shall be allocated with a zero size, and the
+ *  metadata containers constructed. All other pointers in the structure shall be
+ *  initialized to NULL.
+ *
+ *  @return pmChip*    newly allocated pmChip
+ */
+pmChip *pmChipAlloc(
+    pmFPA *fpa,                         ///< FPA to which the chip belongs
+    const char *name                    ///< Name of chip
+);
+
+/** Allocates a pmFPA
+ *
+ *  The constructor shall make an empty pmFPA. The chips array shall be
+ *  allocated with a zero size, the camera and db pointers set to the values
+ *  provided, and the concepts metadata constructed. All other pointers in the
+ *  structure shall be initialized to NULL.
+ *
+ */
+pmFPA *pmFPAAlloc(
+    const psMetadata *camera            ///< Camera configuration
+);
+
+/** Allocates a p_pmHDU
+ *
+ * XXX: More detailed description
+ *
+ * @return p_pmHDU*    newly allocated p_pmHDU
+ */
+p_pmHDU *p_pmHDUAlloc(const char *extname // Extension name
+                     );
+
+
+/** Verify parent links.
+ *
+ *  This function checks the validity of the parent links in the FPA hierarchy.
+ *  If a parent link is not set (or not set correctly), it is corrected, and the
+ *  function shall return false. If all the parent pointers were correct, the
+ *  function shall return true.
+ *
+ */
+bool pmFPACheckParents(
+    pmFPA *fpa
+);
+
+
+
+/** FUNC DESC
+ *
+ *
+ *
+ *
+ */
+
+
+
+/**
+ *
+ * pmFPASelectChip shall set valid to true for the specified chip number
+ * (chipNum), and all other chips shall have valid set to false. In the event
+ * that the specified chip number does not exist within the fpa, the function
+ * shall return false.
+ *
+ */
+bool pmFPASelectChip(
+    pmFPA *fpa,
+    int chipNum
+);
+
+bool pmChipSelectCell(pmChip *chip,
+                      int cellNum
+                     );
+
+/**
+ *
+ * pmFPAExcludeChip shall set valid to false only for the specified chip
+ * number (chipNum). In the event that the specified chip number does not exist
+ * within the fpa, the function shall generate a warning, and perform no action.
+ * The function shall return the number of chips within the fpa that have valid
+ * set to true.
+ *
+ */
+int pmFPAExcludeChip(
+    pmFPA *fpa,
+    int chipNum
+);
+
+int pmChipExcludeCell(pmChip *chip,
+                      int cellNum
+                     );
+
+// Set the weights and masks within a cell, based on the gain and RN
+bool pmCellSetWeights(pmCell *cell // Cell for which to set weights
+                     );
+
+
+
+#endif // #ifndef PM_FPA_H
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAAstrometry.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAAstrometry.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAAstrometry.c	(revision 6448)
@@ -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: /branches/rel10_ifa/psModules/src/astrom/pmFPAAstrometry.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAAstrometry.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAAstrometry.h	(revision 6448)
@@ -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: /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsGet.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsGet.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsGet.c	(revision 6448)
@@ -0,0 +1,1197 @@
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include "pslib.h"
+
+#include "pmAstrometry.h"
+#include "pmFPAConceptsGet.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Private functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+psMetadataItem *p_pmFPAConceptGetFromCamera(pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                           )
+{
+    if (cell) {
+        psMetadata *camera = cell->camera;      // Camera data
+        psMetadataItem *item = psMetadataLookup(camera, concept);
+        return item;
+    }
+    return NULL;
+}
+
+
+psMetadataItem *p_pmFPAConceptGetFromHeader(pmFPA *fpa, // The FPA that contains the chip
+        pmChip *chip, // The chip that contains the cell
+        pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                           )
+{
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *translation = psMetadataLookupMD(&mdStatus, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdStatus) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return NULL;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdStatus, translation, concept);
+    if (mdStatus && strlen(keyword) > 0) {
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psMetadataItem *cellItem = psMetadataLookup(cell->hdu->header, keyword);
+            if (cellItem) {
+                // XXX: Need to clean up before returning
+                return cellItem;
+            }
+        }
+
+        if (chip && chip->hdu) {
+            psMetadataItem *chipItem = psMetadataLookup(chip->hdu->header, keyword);
+            if (chipItem) {
+                // XXX: Need to clean up before returning
+                return chipItem;
+            }
+        }
+
+        if (fpa->hdu) {
+            psMetadataItem *fpaItem = psMetadataLookup(fpa->hdu->header, keyword);
+            if (fpaItem) {
+                // XXX: Need to clean up before returning
+                return fpaItem;
+            }
+        }
+
+        if (fpa->phu) {
+            psMetadataItem *fpaItem = psMetadataLookup(fpa->phu, keyword);
+            if (fpaItem) {
+                // XXX: Need to clean up before returning
+                return fpaItem;
+            }
+        }
+    }
+
+    // No header value
+    return NULL;
+}
+
+
+// Look for a default
+psMetadataItem *p_pmFPAConceptGetFromDefault(pmFPA *fpa, // The FPA that contains the chip
+        pmChip *chip, // The chip that contains the cell
+        pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                            )
+{
+    bool mdOK = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return NULL;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept);
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return NULL;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = p_pmFPAConceptGetFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept);
+                return NULL;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V);    // This is now what we were after
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return defItem;                     // defItem is either NULL or points to what was desired
+}
+
+
+// Look for a database lookup
+// XXX: Not tested
+psMetadataItem *p_pmFPAConceptGetFromDB(pmFPA *fpa, // The FPA that contains the chip
+                                        pmChip *chip, // The chip that contains the cell
+                                        pmCell *cell, // The cell
+                                        psDB *db,       // DB handle
+                                        const char *concept // Name of concept
+                                       )
+{
+    if (! db) {
+        // No database initialised
+        return NULL;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        //        const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // List of value names for the columns
+            psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
+            if (cols->n != values->n) {
+                psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
+                         "the same number of entries --- ignored.\n", concept);
+            } else {
+                // Iterators for the lists
+                psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
+                psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+                char *column = NULL;    // Name of the column
+                while ((column = psListGetAndIncrement(colsIter))) {
+                    char *name = psListGetAndIncrement(valuesIter); // Name for the value
+                    if (!strlen(column) || !strlen(name)) {
+                        psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
+                                 " empty --- ignored.\n", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } else {
+                            // We need to create a new psMetadataItem.  I don't think we can't simply hack
+                            // the existing one, since that could conceivably cause memory leaks
+                            psMetadataItem *newItem = psMetadataItemAlloc(concept, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    psFree(column);
+                } // Iterating through the columns
+                psFree(colsIter);
+                psFree(valuesIter);
+
+                psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
+                // Note that we use limit=2 in order to test if there are multiple rows returned
+
+                psMetadataItem *result = NULL; // The final result of the DB lookup
+                if (dbResult->n == 0) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n",
+                             concept);
+                } else {
+                    if (dbResult-> n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " using the first one only.\n", concept);
+                    }
+                    result = (psMetadataItem*)dbResult->data[0];
+                }
+                // XXX: Need to clean up before returning
+                return result;
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here.\n");
+    return NULL;
+}
+
+
+// Concept lookup
+psMetadataItem *p_pmFPAConceptGet(pmFPA *fpa, // The FPA
+                                  pmChip *chip,// The chip
+                                  pmCell *cell, // The cell
+                                  psDB *db, // DB handle
+                                  const char *concept // Concept name
+                                 )
+{
+    // Try headers, database, defaults in order
+    psMetadataItem *item = p_pmFPAConceptGetFromCamera(cell, concept);
+    if (! item) {
+        item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, concept);
+    }
+    if (! item) {
+        item = p_pmFPAConceptGetFromDB(fpa, chip, cell, db, concept);
+    }
+    if (! item) {
+        item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, concept);
+    }
+    return item; // item is either NULL, or points to what was desired
+}
+
+
+void p_pmFPAConceptGetF32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    float value = NAN;
+    if (item) {
+        switch (item->type) {
+        case PS_DATA_F32:
+            value = item->data.F32;
+            break;
+        case PS_DATA_F64:
+            // Assume it's OK to truncate to floating point from double
+            value = (float)item->data.F64;
+            break;
+        case PS_DATA_S32:
+            // Promote to float
+            value = (float)item->data.S32;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
+                    "undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F32 | PS_META_REPLACE, comment, value);
+}
+
+void p_pmFPAConceptGetF64(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    double value = NAN;
+    if (item) {
+        switch (item->type) {
+        case PS_TYPE_F64:
+            value = item->data.F64;
+            break;
+        case PS_TYPE_F32:
+            // Promote to double
+            value = (double)item->data.F32;
+            break;
+        case PS_TYPE_S32:
+            // Promote to double
+            value = (double)item->data.S32;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
+                    "--- treating as undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F64 | PS_META_REPLACE, comment, value);
+}
+
+void p_pmFPAConceptGetS32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    int value = 0;
+    if (item) {
+        switch (item->type) {
+        case PS_TYPE_S32:
+            value = item->data.S32;
+            break;
+        case PS_TYPE_F32:
+            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
+                     name, comment);
+            value = (int)item->data.F32;
+            break;
+        case PS_TYPE_F64:
+            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
+                     name, comment);
+            value = (int)item->data.F64;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
+                    "undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %d\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_S32 | PS_META_REPLACE, comment, value);
+}
+
+void p_pmFPAConceptGetString(pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psDB *db,  // DB handle
+                             psMetadata *concepts, // The concepts MD
+                             const char *name, // Name of the concept
+                             const char *comment // Comment for concept
+                            )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    psString value = NULL;
+    if (item) {
+        switch (item->type) {
+        case PS_DATA_STRING:
+            value = psMemIncrRefCounter(item->data.V);
+            break;
+        case PS_DATA_F32:
+            psStringAppend(&value, "%f", item->data.F32);
+            break;
+        case PS_DATA_S32:
+            psStringAppend(&value, "%d", item->data.S32);
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
+                    "undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+        value = psStringCopy("");
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %s\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_DATA_STRING | PS_META_REPLACE, comment, value);
+    psFree(value);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Ingest concepts for the FPA
+void pmFPAIngestConcepts(pmFPA *fpa,    // The FPA
+                         psDB *db       // DB handle
+                        )
+{
+    if (! fpa->concepts) {
+        fpa->concepts = psMetadataAlloc();
+    }
+
+    // FPA.NAME
+    p_pmFPAConceptGetString(fpa, NULL, NULL, db, fpa->concepts, "FPA.NAME", "Name of FPA");
+
+    // FPA.AIRMASS
+    p_pmFPAConceptGetF32(fpa, NULL, NULL, db, fpa->concepts, "FPA.AIRMASS", "Airmass at boresight");
+
+    // FPA.FILTER
+    p_pmFPAConceptGetString(fpa, NULL, NULL, db, fpa->concepts, "FPA.FILTER", "Filter used");
+
+    // FPA.POSANGLE
+    p_pmFPAConceptGetF32(fpa, NULL, NULL, db, fpa->concepts, "FPA.POSANGLE", "Position angle for instrument");
+
+    // FPA.RADECSYS
+    p_pmFPAConceptGetString(fpa, NULL, NULL, db, fpa->concepts, "FPA.RADECSYS", "Celestial coordinate system");
+
+    // These take some extra work
+
+    // FPA.RA
+    {
+        double ra = NAN;                // The RA
+        psMetadataItem *raItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.RA"); // The FPA.RA item
+        if (raItem)
+        {
+            switch (raItem->type) {
+            case PS_TYPE_F32:
+                ra = raItem->data.F32;
+                break;
+            case PS_TYPE_F64:
+                ra = raItem->data.F64;
+                break;
+            case PS_DATA_STRING:
+                // Sexagesimal format
+                {
+                    int big, medium;
+                    float small;
+                    // XXX: Upgrade path is to allow dd:mm.mmm
+                    if (sscanf(raItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                            sscanf(raItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
+                    {
+                        psError(PS_ERR_IO, true, "Cannot interpret FPA.RA: %s\n", raItem->data.V);
+                        break;
+                    }
+                    ra = abs(big) + (float)medium/60.0 + small/3600.0;
+                    if (big < 0)
+                    {
+                        ra *= -1.0;
+                    }
+                }
+                break;
+            default:
+                psError(PS_ERR_IO, true, "FPA.RA is of an unexpected type: %x\n", raItem->type);
+            }
+
+            // How to interpret the RA
+            const psMetadata *camera = fpa->camera; // Camera configuration data
+            bool mdok = true;           // Status of MD lookup
+            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+            if (mdok && formats) {
+                psString raFormat = psMetadataLookupStr(&mdok, formats, "FPA.RA");
+                if (mdok && strlen(raFormat) > 0) {
+                    if (strcasecmp(raFormat, "HOURS") == 0) {
+                        ra *= M_PI / 12.0;
+                    } else if (strcasecmp(raFormat, "DEGREES") == 0) {
+                        ra *= M_PI / 180.0;
+                    } else if (strcasecmp(raFormat, "RADIANS") == 0) {
+                        // No action required
+                    } else {
+                        psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
+                                 " HOURS.\n");
+                        ra *= M_PI / 12.0;
+                    }
+                } else {
+                    psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
+                    ra *= M_PI / 12.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                        "assuming format for FPA.RA is HOURS.\n");
+                ra *= M_PI / 12.0;
+            }
+        } else
+        {
+            psError(PS_ERR_IO, false, "Couldn't find FPA.RA.\n");
+        }
+
+        psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_DATA_F64 | PS_META_REPLACE,
+                      "Right Ascension of the boresight (radians)", ra);
+
+    }
+
+    // FPA.DEC
+    {
+        double dec = NAN;               // The DEC
+        psMetadataItem *decItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.DEC"); // The FPA.DEC item
+        if (decItem) {
+            switch (decItem->type) {
+            case PS_TYPE_F32:
+                dec = decItem->data.F32;
+                break;
+            case PS_TYPE_F64:
+                dec = decItem->data.F64;
+                break;
+            case PS_DATA_STRING:
+                // Sexagesimal format
+                {
+                    int big, medium;
+                    float small;
+                    // XXX: Upgrade path is to allow dd:mm.mmm
+                    if (sscanf(decItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                            sscanf(decItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
+                    {
+                        psError(PS_ERR_IO, true, "Cannot interpret FPA.DEC: %s\n", decItem->data.V);
+                        break;
+                    }
+                    dec = abs(big) + (float)medium/60.0 + small/3600.0;
+                    if (big < 0)
+                    {
+                        dec *= -1.0;
+                    }
+                }
+                break;
+            default:
+                psError(PS_ERR_IO, true, "FPA.DEC is of an unexpected type: %x\n", decItem->type);
+            }
+
+            // How to interpret the DEC
+            const psMetadata *camera = fpa->camera; // Camera configuration data
+            bool mdok = true;           // Status of MD lookup
+            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+            if (mdok && formats) {
+                psString decFormat = psMetadataLookupStr(&mdok, formats, "FPA.DEC");
+                if (mdok && strlen(decFormat) > 0) {
+                    if (strcasecmp(decFormat, "HOURS") == 0) {
+                        dec *= M_PI / 12.0;
+                    } else if (strcasecmp(decFormat, "DEGREES") == 0) {
+                        dec *= M_PI / 180.0;
+                    } else if (strcasecmp(decFormat, "RADIANS") == 0) {
+                        // No action required
+                    } else {
+                        psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- "
+                                 "assuming DEGREES.\n");
+                        dec *= M_PI / 180.0;
+                    }
+                } else {
+                    psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
+                    dec *= M_PI / 180.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FORMATS metadata in camera configuration --- "
+                        "assuming format for FPA.DEC is DEGREES.\n");
+                dec *= M_PI / 180.0;
+            }
+        } else {
+            psError(PS_ERR_IO, false, "Couldn't find FPA.DEC.\n");
+        }
+
+        psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_DATA_F64 | PS_META_REPLACE,
+                      "Declination of the boresight (radians)", dec);
+
+    }
+
+    // Pau.
+}
+
+
+// Ingest concepts for the chip
+bool pmChipIngestConcepts(pmChip *chip, // The chip
+                          psDB *db      // DB handle
+                         )
+{
+    //    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    if (! chip->concepts) {
+        chip->concepts = psMetadataAlloc();
+    }
+
+    // CHIP.NAME --- added by pmFPAConstruct
+
+    // Pau.
+    return true;
+}
+
+
+// Add corrective to a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
+static bool correctPosition(pmFPA *fpa, // FPA, contains the camera configuration
+                            pmCell *cell, // Cell containing the concept to correct
+                            const char *conceptName // Name of concept to correct
+                           )
+{
+    bool mdok = false;              // Result of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, fpa->camera, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, conceptName);
+        if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+            psMetadataItem *valueItem = psMetadataLookup(cell->concepts, conceptName);
+            valueItem->data.S32 -= 1;
+        }
+    }
+    return true;
+}
+
+bool p_pmCellIngestConcept(pmFPA *fpa,  // The FPA
+                           pmChip *chip, // The chip
+                           pmCell *cell, // The cell
+                           psDB *db,    // DB handle
+                           const char *concept // Name of the concept
+                          )
+{
+    if (strcmp(concept, "CELL.GAIN") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.GAIN", "CCD gain (e/count)");
+    } else if (strcmp(concept, "CELL.READNOISE") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.READNOISE", "CCD read noise (e)");
+    } else if (strcmp(concept, "CELL.SATURATION") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.SATURATION",
+                             "Saturation level (ADU)");
+    } else if (strcmp(concept, "CELL.BAD") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.BAD", "Bad level (ADU)");
+    } else if (strcmp(concept, "CELL.XPARITY") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.XPARITY",
+                             "Orientation in x compared to the rest of the FPA");
+    } else if (strcmp(concept, "CELL.YPARITY") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.YPARITY",
+                             "Orientation in y compared to the rest of the FPA");
+    } else if (strcmp(concept, "CELL.READDIR") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.READDIR",
+                             "Read direction: 1=row, 2=col");
+    } else if (strcmp(concept, "CELL.EXPOSURE") == 0) { // used to be READOUT.EXPOSURE
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.EXPOSURE", "Exposure time (sec)");
+    } else if (strcmp(concept, "CELL.DARKTIME") == 0) { // used to be READOUT.DARKTIME
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.DARKTIME",
+                             "Time since CCD flush (sec)");
+        // These take some extra work
+    } else if (strcmp(concept, "CELL.TRIMSEC") == 0) {
+        psRegion *trimsec = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by value)
+
+        psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC");
+        if (! secItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.\n");
+            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+        } else if (secItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not of type STR (%x)\n", secItem->type);
+            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+        } else {
+            psString section = secItem->data.V; // The section string
+
+            psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
+            if (! sourceItem) {
+                psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
+                *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+            } else if (sourceItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+                *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+            } else {
+                psString source = sourceItem->data.V; // The source string
+
+                if (strcasecmp(source, "VALUE") == 0) {
+                    *trimsec = psRegionFromString(section);
+                } else if (strcasecmp(source, "HEADER") == 0) {
+                    psMetadata *header = NULL; // The FITS header
+                    if (cell->hdu) {
+                        header = cell->hdu->header;
+                    } else if (chip->hdu) {
+                        header = chip->hdu->header;
+                    } else if (fpa->hdu) {
+                        header = fpa->hdu->header;
+                    }
+                    if (! header) {
+                        psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+                    } else {
+                        bool mdok = true;               // Status of MD lookup
+                        psString secValue = psMetadataLookupStr(&mdok, header, section);
+                        if (! mdok || ! secValue) {
+                            psError(PS_ERR_IO, false, "Unable to locate header %s\n", section);
+                            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+                        } else {
+                            *trimsec = psRegionFromString(secValue);
+                        }
+                    }
+                } else {
+                    psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
+                            "VALUE.\n", source);
+                    *trimsec = psRegionFromString(section);
+                } // Value of CELL.TRIMSEC.SOURCE
+            } // Looking up CELL.TRIMSEC.SOURCE
+        } // Looking up CELL.TRIMSEC
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC", PS_DATA_UNKNOWN | PS_META_REPLACE,
+                      "Trim section", trimsec);
+        psFree(trimsec);
+
+    } else if (strcmp(concept, "CELL.BIASSEC") == 0) {
+        psList *biassecs = psListAlloc(NULL); // List of bias sections
+
+        psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC");
+        if (! secItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.\n");
+        } else if (secItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_IO, true, "CELL.BIASSEC is not of type STR (%x)\n", secItem->type);
+        } else {
+            psString sections = secItem->data.V; // The section string
+
+            psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
+            if (! sourceItem) {
+                psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
+            } else if (sourceItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+            } else {
+                psString source = sourceItem->data.V; // The source string
+
+                psList *secList = psStringSplit(sections, " ;"); // List of sections
+                psListIterator *secIter = psListIteratorAlloc(secList, PS_LIST_HEAD, false); // Iterator over
+                // sections
+                psString aSection = NULL; // A section from the list
+                while ((aSection = psListGetAndIncrement(secIter))) {
+                    psRegion *region = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed
+                    // by value)
+
+                    if (strcasecmp(source, "VALUE") == 0) {
+                        *region = psRegionFromString(aSection);
+                    } else if (strcasecmp(source, "HEADER") == 0) {
+                        psMetadata *header = NULL; // The FITS header
+                        if (cell->hdu) {
+                            header = cell->hdu->header;
+                        } else if (chip->hdu) {
+                            header = chip->hdu->header;
+                        } else if (fpa->hdu) {
+                            header = fpa->hdu->header;
+                        }
+                        if (! header) {
+                            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                            *region = psRegionSet(0.0,0.0,0.0,0.0);
+                        } else {
+                            bool mdok = true;           // Status of MD lookup
+                            psString secValue = psMetadataLookupStr(&mdok, header, aSection);
+                            if (! mdok || ! secValue) {
+                                psError(PS_ERR_IO, false, "Unable to locate header %s\n", aSection);
+                                *region = psRegionSet(0.0,0.0,0.0,0.0);
+                            } else {
+                                *region = psRegionFromString(secValue);
+                            }
+                        }
+                    } else {
+                        psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
+                                "VALUE.\n", source);
+                        *region = psRegionFromString(aSection);
+                    } // Value of CELL.BIASSEC.SOURCE
+
+                    psListAdd(biassecs, PS_LIST_TAIL, region);
+                    psFree(region);
+                } // Iterating over multiple sections
+                psFree(secIter);
+                psFree(secList);
+            } // Looking up CELL.BIASSEC.SOURCE
+        } // Looking up CELL.BIASSEC
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.BIASSEC", PS_DATA_LIST | PS_META_REPLACE,
+                      "Bias sections", biassecs);
+        psFree(biassecs);
+
+    } else if (strcmp(concept, "CELL.XBIN") == 0) {
+        int xBin = 1;                   // Binning factor in x
+        psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.XBIN");
+        if (! binItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.XBIN.\n");
+        } else if (binItem->type == PS_DATA_STRING) {
+            psString binString = binItem->data.V; // The string containing the binning
+            if (sscanf(binString, "%d %*d", &xBin) != 1 &&
+                    sscanf(binString, "%d,%*d", &xBin) != 1) {
+                psError(PS_ERR_IO, true, "Unable to read string to get x binning: %s\n", binString);
+            }
+        } else if (binItem->type == PS_TYPE_S32) {
+            xBin = binItem->data.S32;
+        } else {
+            psError(PS_ERR_IO, true, "Note sure how to interpret CELL.XBIN of type %x --- assuming 1.\n",
+                    binItem->type);
+        }
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_TYPE_S32 | PS_META_REPLACE,
+                      "Binning in x", xBin);
+
+    } else if (strcmp(concept, "CELL.YBIN") == 0) {
+        int yBin = 1;                   // Binning factor in y
+        psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.YBIN");
+        if (! binItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.YBIN.\n");
+        } else if (binItem->type == PS_DATA_STRING) {
+            psString binString = binItem->data.V; // The string containing the binning
+            if (sscanf(binString, "%*d %d", &yBin) != 1 &&
+                    sscanf(binString, "%*d,%d", &yBin) != 1) {
+                psError(PS_ERR_IO, true, "Unable to read string to get y binning: %s\n", binString);
+            }
+        } else if (binItem->type == PS_TYPE_S32) {
+            yBin = binItem->data.S32;
+        } else {
+            psError(PS_ERR_IO, true, "Note sure how to interpret CELL.YBIN of type %x --- assuming 1.\n",
+                    binItem->type);
+        }
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_TYPE_S32 | PS_META_REPLACE,
+                      "Binning in y", yBin);
+
+    } else if (strcmp(concept, "CELL.TIME") == 0 || strcmp(concept, "CELL.TIMESYS") == 0) {
+        // Do CELL.TIME and CELL.TIMESYS together
+        psTime *time = NULL;            // The time
+        psTimeType timeSys = PS_TIME_UTC; // The time system
+
+        // CELL.TIMESYS
+        psMetadataItem *sysItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIMESYS");
+        if (! sysItem) {
+            psError(PS_ERR_IO, true, "Couldn't find CELL.TIMESYS --- assuming UTC.\n");
+        } else if (sysItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_IO, true, "CELL.TIMESYS isn't of type STRING --- assuming UTC.\n");
+        } else {
+            psString sys = sysItem->data.V; // The time system string
+            if (strcasecmp(sys, "TAI") == 0) {
+                timeSys = PS_TIME_TAI;
+            } else if (strcasecmp(sys, "UTC") == 0) {
+                timeSys = PS_TIME_UTC;
+            } else if (strcasecmp(sys, "UT1") == 0) {
+                timeSys = PS_TIME_UT1;
+            } else if (strcasecmp(sys, "TT") == 0) {
+                timeSys = PS_TIME_TT;
+            } else {
+                psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
+            }
+        }
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_TYPE_S32 | PS_META_REPLACE,
+                      "Time system", timeSys);
+
+        psMetadataItem *timeItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIME");
+        if (! timeItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.TIME.\n");
+        } else {
+            // Get format
+            const psMetadata *camera = fpa->camera; // The camera configuration data
+            bool mdok = true;           // Status of MD lookup
+            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+            if (mdok && formats) {
+                psString timeFormat = psMetadataLookupStr(&mdok, formats, "CELL.TIME");
+                if (mdok && strlen(timeFormat) > 0) {
+                    switch (timeItem->type) {
+                    case PS_DATA_STRING: {
+                            psString timeString = timeItem->data.V;     // String with the time
+                            if (strcasecmp(timeFormat, "ISO") == 0) {
+                                // timeString contains an ISO time
+                                time = psTimeFromISO(timeString, timeSys);
+                            } else if (strcasecmp(timeFormat, "JD") == 0) {
+                                double timeValue = strtod (timeString, NULL);
+                                time = psTimeFromJD(timeValue);
+                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                                double timeValue = strtod (timeString, NULL);
+                                time = psTimeFromMJD(timeValue);
+                            } else if (strstr(timeFormat, "SEPARATE")) {
+                                // timeString contains headers for the date and time
+                                psMetadata *header = NULL; // The FITS header
+                                if (cell->hdu) {
+                                    header = cell->hdu->header;
+                                } else if (chip->hdu) {
+                                    header = chip->hdu->header;
+                                } else if (fpa->hdu) {
+                                    header = fpa->hdu->header;
+                                }
+                                if (! header) {
+                                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                                } else {
+                                    // Get the headers
+                                    char *stuff1 = strpbrk(timeString, " ,;");
+                                    psString dateName = psStringNCopy(timeString,
+                                                                      strlen(timeString) - strlen(stuff1));
+                                    char *stuff2 = strpbrk(stuff1, "abcdefghijklmnopqrstuvwxyz"
+                                                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+                                    psString timeName = psStringCopy(stuff2);
+
+                                    bool mdok = true; // Status of MD lookup
+                                    psString dateString = psMetadataLookupStr(&mdok, header, dateName);
+                                    psFree(dateName);
+                                    int day = 0, month = 0, year = 0;
+                                    if (sscanf(dateString, "%d-%d-%d", &day, &month, &year) != 3 &&
+                                            sscanf(dateString, "%d/%d/%d", &day, &month, &year) != 3) {
+                                        psError(PS_ERR_IO, true, "Unable to read date: %s\n", dateString);
+                                    } else {
+                                        if (strstr(timeFormat, "BACKWARDS")) {
+                                            int temp = day;
+                                            day = year;
+                                            year = temp;
+                                        }
+                                        if (strstr(timeFormat, "PRE2000") || year < 2000) {
+                                            year += 2000;
+                                        }
+
+                                        psMetadataItem *timeItem = psMetadataLookup(header, timeName);
+                                        if (! timeItem) {
+                                            psError(PS_ERR_IO, false, "Unable to find time header: %s\n",
+                                                    timeName);
+                                        } else if (timeItem->type == PS_DATA_STRING) {
+                                            // Time is a string, in the usual way:
+                                            psStringAppend(&dateString, "T%s", timeItem->data.V);
+                                        } else {
+                                            // Assume that time is specified in Second of Day
+                                            double seconds = NAN;
+                                            switch (timeItem->type) {
+                                            case PS_TYPE_S32:
+                                                seconds = timeItem->data.S32;
+                                                break;
+                                            case PS_TYPE_F32:
+                                                seconds = timeItem->data.F32;
+                                                break;
+                                            case PS_TYPE_F64:
+                                                seconds = timeItem->data.F64;
+                                                break;
+                                            default:
+                                                psError(PS_ERR_IO, true, "Time header (%s) is not of an "
+                                                        "expected type: %x\n", timeName, timeItem->type);
+                                            }
+                                            // Now print to timeString as "hh:mm:ss.ss"
+                                            int hours = seconds / 3600;
+                                            seconds -= (double)hours * 3600.0;
+                                            int minutes = seconds / 60;
+                                            seconds -= (double)minutes * 60.0;
+                                            psStringAppend(&dateString, "T%02d:%02d:%02f", hours, minutes,
+                                                           seconds);
+                                        }
+                                        time = psTimeFromISO(dateString, timeSys);
+                                    } // Reading date and time
+                                    psFree(timeName);
+                                } // Reading headers
+                            } else {
+                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%s) --- trying "
+                                        "ISO\n", timeString);
+                                time = psTimeFromISO(timeString, timeSys);
+                            } // Interpreting the time string
+                        }
+                        break;
+                    case PS_TYPE_F32: {
+                            double timeValue = (double)timeItem->data.F32;
+                            if (strcasecmp(timeFormat, "JD") == 0) {
+                                time = psTimeFromJD(timeValue);
+                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                                time = psTimeFromMJD(timeValue);
+                            } else {
+                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                                        "JD\n", timeValue);
+                                time = psTimeFromJD(timeValue);
+                            }
+                        }
+                        break;
+                    case PS_TYPE_F64: {
+                            double timeValue = (double)timeItem->data.F64;
+                            if (strcasecmp(timeFormat, "JD") == 0) {
+                                time = psTimeFromJD(timeValue);
+                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                                time = psTimeFromMJD(timeValue);
+                            } else {
+                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                                        "JD\n", timeValue);
+                                time = psTimeFromJD(timeValue);
+                            }
+                        }
+                        break;
+                    default:
+                        psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
+                    }
+                } else {
+                    psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
+                } // Getting the format
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
+            } // Getting the formats
+        } // Getting CELL.TIME
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIME", PS_DATA_TIME | PS_META_REPLACE,
+                      "Time of exposure", time);
+        psFree(time);
+
+        // These are new and experimental concepts: CELL.X0 and CELL.Y0
+    } else if (strcmp(concept, "CELL.X0") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.X0", "Position of (0,0) on the chip");
+        correctPosition(fpa, cell, "CELL.X0");
+    } else if (strcmp(concept, "CELL.Y0") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.Y0", "Position of (0,0) on the chip");
+        correctPosition(fpa, cell, "CELL.Y0");
+    }
+
+    return true;
+}
+
+
+
+// Ingest concepts for the cell
+bool pmCellIngestConcepts(pmCell *cell, // The cell
+                          psDB *db      // DB handle
+                         )
+{
+    pmChip *chip = cell->parent;        // The parent chip
+    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    if (! cell->concepts) {
+        cell->concepts = psMetadataAlloc();
+    }
+
+    // CELL.NAME --- added by pmFPAConstruct
+
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.GAIN");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.READNOISE");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.SATURATION");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.BAD");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.XPARITY");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.YPARITY");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.READDIR");
+
+    // These used to be pmReadoutGetExposure and pmReadoutGetDarkTime, but that doesn't really make sense at
+    // the moment.  Maybe we need to add a "parent" link to the readouts.  But then how are the exposure times
+    // REALLY derived?  They're not in the FITS headers, because a readout is a plane in a 3D image.  We'll
+    // have to dream up some additional suffix to specify these, but for now....
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.EXPOSURE");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.DARKTIME");
+
+
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.TRIMSEC");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.BIASSEC");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.TIME"); // This also does CELL.TIMESYS
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.XBIN");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.YBIN");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.X0");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.Y0");
+
+    return true;
+}
+
+
+// Retrieve a concept
+psMetadataItem *pmCellGetConcept(pmCell *cell, // The cell
+                                 const char *concept // The concept
+                                )
+{
+    psMetadataItem *item = psMetadataLookup(cell->concepts, concept);
+    if (! item) {
+        pmChip *chip = cell->parent;
+        item = psMetadataLookup(chip->concepts, concept);
+        if (! item) {
+            pmFPA *fpa = chip->parent;
+            item = psMetadataLookup(fpa->concepts, concept);
+        }
+    }
+    return item;                        // item is either NULL or is what we want.
+}
+
+
+// Set up concepts for blank image
+bool pmFPABlankConcepts(pmFPA *fpa      // The FPA
+                       )
+{
+    assert(fpa);
+
+    // FPA-level concepts
+    if (! fpa->concepts) {
+        fpa->concepts = psMetadataAlloc();
+    }
+    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.NAME", PS_META_REPLACE, "Name of FPA", "");
+    psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.AIRMASS", PS_META_REPLACE,
+                     "Airmass at boresight", NAN);
+    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.FILTER", PS_META_REPLACE, "Filter used", "");
+    psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.POSANGLE", PS_META_REPLACE,
+                     "Position angle for instrument", NAN);
+    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.RADECSYS", PS_META_REPLACE,
+                     "Celestial coordinate system", "");
+    psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_META_REPLACE,
+                     "Right Ascension of the boresight (radians)", NAN);
+    psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_META_REPLACE,
+                     "Declination of the boresight (radians)", NAN);
+
+    // Iterate through the chips
+    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) {
+            continue;
+        }
+
+        // Chip-level concepts
+        if (! chip->concepts) {
+            chip->concepts = psMetadataAlloc();
+        }
+        // CHIP.NAME *should* have been added by pmFPAConstruct --- make sure
+        psMetadataItem *chipNameItem = psMetadataLookup(chip->concepts, "CHIP.NAME");
+        if (! chipNameItem) {
+            psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "CHIP.NAME", PS_META_REPLACE, "Name of Chip", "");
+        }
+
+        // Iterate through the cells
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[i]; // The cell of interest
+            if (! cell) {
+                continue;
+            }
+
+            // Cell-level concepts
+            if (! cell->concepts) {
+                cell->concepts = psMetadataAlloc();
+            }
+            // CELL.NAME *should* have been added by pmFPAConstruct --- make sure
+            psMetadataItem *cellNameItem = psMetadataLookup(cell->concepts, "CELL.NAME");
+            if (!cellNameItem) {
+                psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "CELL.NAME", PS_META_REPLACE,
+                                 "Name of Cell", "");
+            }
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.GAIN", PS_META_REPLACE, "CCD gain (e/count)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.READNOISE", PS_META_REPLACE, "CCD read noise (e)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.SATURATION", PS_META_REPLACE, "Saturation level (ADU)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.BAD", PS_META_REPLACE, "Bad level (ADU)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.READDIR", PS_META_REPLACE, "Read direction: 1=row, 2=col", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "Exposure time (sec)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.DARKTIME", PS_META_REPLACE, "Time since CCD flush (sec)", NAN);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_META_REPLACE, "Binning in x", 0);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_META_REPLACE, "Binning in y", 0);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.X0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.Y0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+
+            psRegion *trimsec = psAlloc(sizeof(psRegion)); // Dummy region
+            trimsec->x0 = NAN;
+            trimsec->x1 = NAN;
+            trimsec->y0 = NAN;
+            trimsec->y1 = NAN;
+            psMetadataAddPtr(cell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC", PS_META_REPLACE, "Trim section", trimsec);
+            psList *biassecs = psListAlloc(NULL); // Dummy list
+            psMetadataAddPtr(cell->concepts, PS_LIST_TAIL, "CELL.BIASSEC", PS_META_REPLACE, "Bias section", biassecs);
+            psTime *time = psTimeAlloc(PS_TIME_TAI); // Dummy time
+            psMetadataAddPtr(cell->concepts, PS_LIST_TAIL, "CELL.TIME", PS_META_REPLACE, "Time of exposure", time);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_META_REPLACE, "Time system", PS_TIME_TAI);
+        }
+    }
+
+    return true;
+}
+
+// Copy concepts from one FPA to another
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      )
+{
+    // Copy FPA concepts
+    target->concepts = pap_psMetadataCopy(target->concepts, source->concepts);
+
+    // Copy chip concepts
+    psArray *targetChips = target->chips; // Chips in target
+    psArray *sourceChips = source->chips; // Chips in source
+    if (targetChips->n != sourceChips->n) {
+        psError(PS_ERR_IO, true, "Number of chips in target (%d) and source (%d) differ --- unable to copy "
+                "concepts.\n", targetChips->n, sourceChips->n);
+        return false;
+    }
+    for (int i = 0; i < targetChips->n; i++) {
+        pmChip *targetChip = targetChips->data[i]; // Target chip of interest
+        pmChip *sourceChip = sourceChips->data[i]; // Source chip of interest
+        if (! targetChip || ! sourceChip) {
+            continue;
+        }
+        targetChip->concepts = pap_psMetadataCopy(targetChip->concepts, sourceChip->concepts);
+
+        // Copy cell concepts
+        psArray *targetCells = targetChip->cells; // Cells in target
+        psArray *sourceCells = sourceChip->cells; // Cells in source
+        if (targetCells->n != sourceCells->n) {
+            psError(PS_ERR_IO, true, "Number of cells in target (%d) and source (%d) differ for chip %d ---"
+                    " unable to copy concepts.\n", targetCells->n, sourceCells->n, i);
+            return false;
+        }
+        for (int j = 0; j < targetCells->n; j++) {
+            pmCell *targetCell = targetCells->data[j]; // Target chip of interest
+            pmCell *sourceCell = sourceCells->data[j]; // Source chip of interest
+            if (! targetCell || ! sourceCell) {
+                continue;
+            }
+            targetCell->concepts = pap_psMetadataCopy(targetCell->concepts, sourceCell->concepts);
+        }
+    }
+
+    return true;
+}
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsGet.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsGet.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsGet.h	(revision 6448)
@@ -0,0 +1,103 @@
+#ifndef PM_FPA_CONCEPTS_GET_H
+#define PM_FPA_CONCEPTS_GET_H
+
+#include "pslib.h"
+#include "pmAstrometry.h"
+#include "psAdditionals.h"
+
+psMetadataItem *p_pmFPAConceptGetFromCamera(pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                           );
+psMetadataItem *p_pmFPAConceptGetFromHeader(pmFPA *fpa, // The FPA that contains the chip
+        pmChip *chip, // The chip that contains the cell
+        pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                           );
+psMetadataItem *p_pmFPAConceptGetFromDefault(pmFPA *fpa, // The FPA that contains the chip
+        pmChip *chip, // The chip that contains the cell
+        pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                            );
+psMetadataItem *p_pmFPAConceptGetFromDB(pmFPA *fpa, // The FPA that contains the chip
+                                        pmChip *chip, // The chip that contains the cell
+                                        pmCell *cell, // The cell
+                                        psDB *db,       // DB handle
+                                        const char *concept // Name of concept
+                                       );
+psMetadataItem *p_pmFPAConceptGet(pmFPA *fpa, // The FPA
+                                  pmChip *chip,// The chip
+                                  pmCell *cell, // The cell
+                                  psDB *db, // DB handle
+                                  const char *concept // Concept name
+                                 );
+void p_pmFPAConceptGetF32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         );
+void p_pmFPAConceptGetF64(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         );
+void p_pmFPAConceptGetS32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         );
+void p_pmFPAConceptGetString(pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psDB *db,  // DB handle
+                             psMetadata *concepts, // The concepts MD
+                             const char *name, // Name of the concept
+                             const char *comment // Comment for concept
+                            );
+
+
+// Ingest a single cell concept; this is the workhorse for ingesting cell concepts
+bool p_pmCellIngestConcept(pmFPA *fpa,  // The FPA
+                           pmChip *chip, // The chip
+                           pmCell *cell, // The cell
+                           psDB *db,    // DB handle
+                           const char *concept // Name of the concept
+                          );
+
+// Ingest concepts for the FPA
+void pmFPAIngestConcepts(pmFPA *fpa,    // The FPA
+                         psDB *db       // DB handle
+                        );
+// Ingest concepts for the chip
+bool pmChipIngestConcepts(pmChip *chip, // The chip
+                          psDB *db      // DB handle
+                         );
+// Ingest concepts for the cell
+bool pmCellIngestConcepts(pmCell *cell, // The cell
+                          psDB *db      // DB handle
+                         );
+
+// Set up concepts for blank image
+bool pmFPABlankConcepts(pmFPA *fpa      // The FPA
+                       );
+
+// Copy concepts from one FPA to another
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      );
+
+// Retrieve a concept
+psMetadataItem *pmCellGetConcept(pmCell *cell, // The cell
+                                 const char *concept // The concept
+                                );
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsSet.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsSet.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsSet.c	(revision 6448)
@@ -0,0 +1,904 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmAstrometry.h"
+#include "pmFPAConceptsGet.h"
+#include "pmFPAConceptsSet.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define COMPARE_REGIONS(a,b) (((a)->x0 == (b)->x0 && \
+                               (a)->x1 == (b)->x1 && \
+                               (a)->y0 == (b)->y0 && \
+                               (a)->y1 == (b)->y1) ? true : false)
+
+
+static bool compareConcepts(psMetadataItem *item1, // First item to compare
+                            psMetadataItem *item2 // Second item to compare
+                           )
+{
+    // First order checks
+    if (! item1 || ! item2) {
+        return false;
+    }
+    if (strcasecmp(item1->name, item2->name) != 0) {
+        return false;
+    }
+
+    // Check the more boring types
+    switch (item1->type) {
+    case PS_TYPE_S32:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.S32 == item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.S32 == (int)item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.S32 == (int)item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+    case PS_TYPE_F32:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.F32 == (float)item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.F32 == item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.F32 == (float)item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+    case PS_TYPE_F64:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.F64 == (double)item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.F64 == (double)item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.F64 == item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+        return (item1->data.F64 == item2->data.F64) ? true : false;
+    case PS_DATA_STRING:
+        if (item2->type != PS_DATA_STRING) {
+            return false;
+        }
+        return (strcasecmp(item1->data.V, item2->data.V) == 0) ? true : false;
+    default:
+        return false;
+    }
+    psAbort(__func__, "Should never get here.\n");
+}
+
+
+// Well, not really "set", but check to make sure it's there and matches
+static bool setConceptInCamera(pmCell *cell, // The cell
+                               psMetadataItem *concept // Concept
+                              )
+{
+    if (cell) {
+        psMetadataItem *item = psMetadataLookup(cell->camera, concept->name); // Info we want
+        return compareConcepts(item, concept);
+    }
+
+    return false;
+}
+
+
+static bool setConceptInHeader(pmFPA *fpa, // The FPA that contains the chip
+                               pmChip *chip, // The chip that contains the cell
+                               pmCell *cell, // The cell
+                               psMetadataItem *concept // Concept
+                              )
+{
+    bool mdok = true;                   // Status of MD lookup
+    bool status = false;                // Status of setting header
+    psMetadata *translation = psMetadataLookupMD(&mdok, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdok) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return false;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdok, translation, concept->name);
+    if (mdok && strlen(keyword) > 0) {
+        psTrace(__func__, 6, "It's in keyword %s\n", keyword);
+        psMetadataItem *headerItem = NULL; // Item to add to header
+        // XXX: Need to expand range of types
+        switch (concept->type) {
+        case PS_DATA_STRING:
+            headerItem = psMetadataItemAllocStr(keyword, concept->comment, concept->data.V);
+            break;
+        case PS_DATA_S32:
+            headerItem = psMetadataItemAllocS32(keyword, concept->comment, concept->data.S32);
+            break;
+        case PS_DATA_F32:
+            headerItem = psMetadataItemAllocF32(keyword, concept->comment, concept->data.F32);
+            break;
+        case PS_DATA_F64:
+            headerItem = psMetadataItemAllocF64(keyword, concept->comment, concept->data.F64);
+            break;
+        default:
+            headerItem = psMetadataItemAlloc(keyword, concept->type, concept->comment,
+                                             concept->data.V); // Item for the header
+        }
+
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psTrace(__func__, 7, "Adding to the cell level header...\n");
+            psMetadataAddItem(cell->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (chip && chip->hdu) {
+            psTrace(__func__, 7, "Adding to the chip level header...\n");
+            psMetadataAddItem(chip->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (fpa->hdu) {
+            psTrace(__func__, 7, "Adding to the FPA level header...\n");
+            psMetadataAddItem(fpa->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else {
+            // In desperation, add to the PHU --- it HAS to be in the header somewhere!
+            if (! fpa->phu) {
+                fpa->phu = psMetadataAlloc();
+            }
+            psTrace(__func__, 7, "Adding to the PHU...\n");
+            psMetadataAddItem(fpa->phu, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        }
+        psFree(headerItem);
+    }
+
+    // No header value
+    return status;
+}
+
+
+// Well, not really "set", but check to see if it's there, and matches
+static bool setConceptInDefault(pmFPA *fpa, // The FPA that contains the chip
+                                pmChip *chip, // The chip that contains the cell
+                                pmCell *cell, // The cell
+                                psMetadataItem *concept // Concept
+                               )
+{
+    bool mdOK = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK || ! defaults) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return false;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept->name);
+    bool status = false;                // Result of checking the database
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept->name);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return false;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = p_pmFPAConceptGetFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept->name);
+                return false;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V); // This is now what we were after
+        }
+
+        status = compareConcepts(defItem, concept);
+        if (! status) {
+            psError(PS_ERR_IO, true, "Concept %s is specified by default in the camera configuration, "
+                    "but doesn't match the actual value.\n", concept->name);
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return status;
+}
+
+
+// XXX: Not tested at all
+static bool setConceptInDB(pmFPA *fpa, // The FPA that contains the chip
+                           pmChip *chip, // The chip that contains the cell
+                           pmCell *cell, // The cell
+                           psDB *db,    // DB handle
+                           psMetadataItem *concept // Concept
+                          )
+{
+    if (! db) {
+        // No database initialised
+        return false;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept->name);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        //        const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // List of value names for the columns
+            psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
+            if (cols->n != values->n) {
+                psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
+                         "the same number of entries --- ignored.\n", concept);
+            } else {
+                // Iterators for the lists
+                psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
+                psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+                char *column = NULL;    // Name of the column
+                while ((column = psListGetAndIncrement(colsIter))) {
+                    char *name = psListGetAndIncrement(valuesIter); // Name for the value
+                    if (!strlen(column) || !strlen(name)) {
+                        psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
+                                 " empty --- ignored.\n", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } else {
+                            // We need to create a new psMetadataItem.  I don't think we can't simply hack
+                            // the existing one, since that could conceivably cause memory leaks
+                            psMetadataItem *newItem = psMetadataItemAlloc(concept->name, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    psFree(column);
+                } // Iterating through the columns
+                psFree(colsIter);
+                psFree(valuesIter);
+
+                // Check first to make sure we're only going to touch one row
+                psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
+                // Note that we use limit=2 in order to test if there are multiple rows returned
+                if (! dbResult || dbResult->n == 0) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n",
+                             concept->name);
+                    return false;
+                } else {
+                    if (dbResult->n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " ignored.\n", concept->name);
+                    }
+                    // Update the DB
+                    psMetadata *update = psMetadataAlloc();
+                    psMetadataAddItem(update, concept, PS_LIST_HEAD, 0);
+                    psDBUpdateRows(db, tableName, selection, update);
+                    psFree(update);
+                    return true;
+                }
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here?\n");
+    return false;
+}
+
+
+// Concept set from item
+static bool setConceptItem(pmFPA *fpa, // The FPA
+                           pmChip *chip,// The chip
+                           pmCell *cell,        // The cell
+                           psDB *db, // DB handle
+                           psMetadataItem *concept // Concept item
+                          )
+{
+    // Try headers, database, defaults in order
+    psTrace(__func__, 3, "Trying to set concept %s...\n", concept->name);
+    bool status = setConceptInCamera(cell, concept); // Status for return
+    if (! status) {
+        psTrace(__func__, 5, "Trying header....\n");
+        status = setConceptInHeader(fpa, chip, cell, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Trying database....\n");
+        status = setConceptInDB(fpa, chip, cell, db, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Checking defaults....\n");
+        status = setConceptInDefault(fpa, chip, cell, concept);
+    }
+
+    if (! status) {
+        psError(PS_ERR_IO, true, "Unable to set %s (%s).\n", concept->name, concept->comment);
+    }
+
+    return status;
+}
+
+
+// Concept set
+static bool setConcept(pmFPA *fpa, // The FPA
+                       pmChip *chip,// The chip
+                       pmCell *cell,    // The cell
+                       psDB *db, // DB handle
+                       psMetadata *concepts, // Concepts MD from which to set
+                       const char *name // Name of the concept
+                      )
+{
+    psMetadataItem *concept = psMetadataLookup(concepts, name);
+    if (! concept) {
+        psError(PS_ERR_IO, true, "No such concept as %s\n", name);
+        return false;
+    }
+    return setConceptItem(fpa, chip, cell, db, concept);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Outget concepts from the FPA
+void pmFPAOutgestConcepts(pmFPA *fpa,   // The FPA
+                          psDB *db      // DB handle
+                         )
+{
+    // FPA.NAME
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.NAME");
+
+    // FPA.AIRMASS
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.AIRMASS");
+
+    // FPA.FILTER
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.FILTER");
+
+    // FPA.POSANGLE
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.POSANGLE");
+
+    // FPA.RADECSYS
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.RADECSYS");
+
+    // These take some extra work
+
+    // FPA.RA
+    {
+        double ra = psMetadataLookupF64(NULL, fpa->concepts, "FPA.RA"); // The RA
+
+        // How to interpret the RA
+        const psMetadata *camera = fpa->camera; // Camera configuration data
+        bool mdok = true;               // Status of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats)
+        {
+            psString raFormat = psMetadataLookupStr(&mdok, formats, "FPA.RA");
+            if (mdok && strlen(raFormat) > 0) {
+                if (strcasecmp(raFormat, "HOURS") == 0) {
+                    ra /= M_PI / 12.0;
+                } else if (strcasecmp(raFormat, "DEGREES") == 0) {
+                    ra /= M_PI / 180.0;
+                } else if (strcasecmp(raFormat, "RADIANS") == 0) {
+                    // No action required
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
+                             " HOURS.\n");
+                    ra /= M_PI / 12.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
+                ra /= M_PI / 12.0;
+            }
+        } else
+        {
+            psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                    "assuming format for FPA.RA is HOURS.\n");
+            ra /= M_PI / 12.0;
+        }
+
+        // We choose to write sexagesimal format
+        int big, medium;
+        float small;
+        big = (int)ra;
+        medium = (int)(60.0*(ra - (double)big));
+        small = 3600.0*(ra - (double)big - 60.0 * (double)medium);
+        psString raString = psStringCopy("");
+        psStringAppend(&raString, "%d:%d:%.2f", big, medium, small);
+        psMetadataItem *raItem = psMetadataItemAllocStr("FPA.RA", "Right Ascension of the boresight",
+                                 raString);
+        setConceptItem(fpa, NULL, NULL, db, raItem);
+        psFree(raItem);
+        psFree(raString);
+    }
+
+    // FPA.DEC
+    {
+        double dec = psMetadataLookupF64(NULL, fpa->concepts, "FPA.DEC"); // The DEC
+
+        // How to interpret the DEC
+        const psMetadata *camera = fpa->camera; // Camera configuration data
+        bool mdok = true;               // Status of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats)
+        {
+            psString decFormat = psMetadataLookupStr(&mdok, formats, "FPA.DEC");
+            if (mdok && strlen(decFormat) > 0) {
+                if (strcasecmp(decFormat, "HOURS") == 0) {
+                    dec /= M_PI / 12.0;
+                } else if (strcasecmp(decFormat, "DEGREES") == 0) {
+                    dec /= M_PI / 180.0;
+                } else if (strcasecmp(decFormat, "RADIANS") == 0) {
+                    // No action required
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- assuming"
+                             " DEGREES.\n");
+                    dec /= M_PI / 180.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
+                dec /= M_PI / 180.0;
+            }
+        } else
+        {
+            psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                    "assuming format for FPA.DEC is HOURS.\n");
+            dec /= M_PI / 12.0;
+        }
+
+        // We choose to write sexagesimal format
+        int big, medium;
+        float small;
+        big = (int)dec;
+        medium = (int)(60.0*(dec - (double)big));
+        small = 3600.0*(dec - (double)big - 60.0 * (double)medium);
+        psString decString = psStringCopy("");
+        psStringAppend(&decString, "%d:%d:%.2f", big, medium, small);
+        psMetadataItem *decItem = psMetadataItemAllocStr("FPA.DEC", "Right Ascension of the boresight",
+                                  decString);
+        setConceptItem(fpa, NULL, NULL, db, decItem);
+        psFree(decItem);
+        psFree(decString);
+
+    }
+
+    // Pau.
+}
+
+
+// Outgest concepts for the chip
+void pmChipOutgestConcepts(pmChip *chip, // The chip
+                           psDB *db     // DB handle
+                          )
+{
+    //    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    // CHIP.NAME --- no need to do anything
+
+    // Pau.
+}
+
+
+// Outgest concepts for the cell
+void pmCellOutgestConcepts(pmCell *cell, // The cell
+                           psDB *db     // DB handle
+                          )
+{
+    pmChip *chip = cell->parent;        // The parent chip
+    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    // CELL.NAME --- no need to do anything
+
+    // CELL.GAIN
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.GAIN");
+
+    // CELL.READNOISE
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.READNOISE");
+
+    // CELL.SATURATION
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.SATURATION");
+
+    // CELL.BAD
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.BAD");
+
+    // CELL.XPARITY
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.XPARITY");
+
+    // CELL.YPARITY
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.YPARITY");
+
+    // CELL.READDIR
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.READDIR");
+
+    // These used to be pmReadoutGetExposure and pmReadoutGetDarkTime, but that doesn't really make sense at
+    // the moment.  Maybe we need to add a "parent" link to the readouts.  But then how are the exposure times
+    // REALLY derived?  They're not in the FITS headers, because a readout is a plane in a 3D image.  We'll
+    // have to dream up some additional suffix to specify these, but for now....
+
+    // CELL.EXPOSURE (used to be READOUT.EXPOSURE)
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.EXPOSURE");
+
+    // CELL.DARKTIME (used to be READOUT.DARKTIME)
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.DARKTIME");
+
+    // These take some extra work
+
+    // CELL.TRIMSEC
+    {
+        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC");
+        psRegion *trimsec = trimsecItem->data.V; // The trimsec region
+        psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
+        if (! sourceItem)
+        {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
+        } else if (sourceItem->type != PS_DATA_STRING)
+        {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+        } else
+        {
+            psString source = sourceItem->data.V; // The source string
+
+            if (strcasecmp(source, "VALUE") == 0) {
+                // Check that it's the same value as stored in the camera
+                psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.TRIMSEC");
+                psRegion checkRegion = psRegionFromString(checkString);
+                if (! COMPARE_REGIONS(&checkRegion, trimsec)) {
+                    psError(PS_ERR_IO, true, "Target CELL.TRIMSEC is specified by value, and values don't "
+                            "match.\n");
+                }
+            } else if (strcasecmp(source, "HEADER") == 0) {
+                //                psString keyword = psMetadataLookupStr(NULL, cell->camera, "CELL.TRIMSEC");
+                psMetadata *header = NULL; // The FITS header
+                if (cell->hdu) {
+                    header = cell->hdu->header;
+                } else if (chip->hdu) {
+                    header = chip->hdu->header;
+                } else if (fpa->hdu) {
+                    header = fpa->hdu->header;
+                }
+                if (! header) {
+                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                } else {
+                    psMetadataAddItem(header, trimsecItem, PS_LIST_TAIL, PS_META_REPLACE);
+                }
+            } else {
+                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
+            }
+        }
+    }
+
+    // CELL.BIASSEC
+    {
+        psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
+        psList *biassecs = biassecItem->data.V; // The biassecs region list
+        psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
+        if (! sourceItem)
+        {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
+        } else if (sourceItem->type != PS_DATA_STRING)
+        {
+            psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+        } else
+        {
+            psString source = sourceItem->data.V; // The source string
+
+            if (strcasecmp(source, "VALUE") == 0) {
+                // Check that it's the same value as stored in the camera
+                psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
+                psList *checkList = psStringSplit(checkString, " ;");
+                if (biassecs->n != checkList->n) {
+                    psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but number of "
+                            "entries doesn't match.\n");
+                } else {
+                    // We don't care if the order matches or not
+                    psVector *check = psVectorAlloc(biassecs->n, PS_TYPE_U8); // Vector to mark regions off
+                    for (int i = 0; i < check->n; i++) {
+                        check->data.U8[i] = 0;
+                    }
+                    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD,
+                                                   false); // Iterator
+                    psListIterator *checkListIter = psListIteratorAlloc(checkList, PS_LIST_HEAD, false);
+                    psRegion *biassec = NULL; // Region from iteration
+                    while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                        psListIteratorSet(checkListIter, PS_LIST_HEAD);
+                        psString checkRegionString = NULL; // Region string from iteration
+                        int i = 0;              // Counter
+                        while ((checkRegionString = psListGetAndIncrement(checkListIter))) {
+                            psRegion checkRegion = psRegionFromString(checkRegionString);
+                            psTrace(__func__, 7, "Checking [%.0f:%.0f,%.0f:%.0f] against "
+                                    "[%.0f:%.0f,%.0f:%.0f]\n", biassec->x0, biassec->x1, biassec->y0,
+                                    biassec->y1, checkRegion.x0, checkRegion.x1, checkRegion.y0,
+                                    checkRegion.y1);
+                            if (COMPARE_REGIONS(biassec, &checkRegion)) {
+                                check->data.U8[i] = 1;
+                                i++;
+                                break;
+                            }
+                            i++;
+                        }
+                    }
+                    for (int i = 0; i < check->n; i++) {
+                        if (check->data.U8[i] == 0) {
+                            psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but values "
+                                    "don't match.\n");
+                            break;
+                        }
+                    }
+                    psFree(checkListIter);
+                    psFree(biassecsIter);
+                    psFree(check);
+                }
+                psFree(checkList);
+            } else if (strcasecmp(source, "HEADER") == 0) {
+                psString keywordsString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
+                psList *keywords = psStringSplit(keywordsString, " ,;");
+                if (biassecs->n != keywords->n) {
+                    psError(PS_ERR_IO, true, "Target CELL.BIASSEC is sepcified by headers, but the number "
+                            "of headers doesn't match.\n");
+                } else {
+                    psMetadata *header = NULL; // The FITS header
+                    if (cell->hdu) {
+                        header = cell->hdu->header;
+                    } else if (chip->hdu) {
+                        header = chip->hdu->header;
+                    } else if (fpa->hdu) {
+                        header = fpa->hdu->header;
+                    }
+                    if (! header) {
+                        psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                    } else {
+                        psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false);
+                        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false);
+                        psString keyword = NULL; // Header keyword from list
+                        while ((keyword = psListGetAndIncrement(keywordsIter))) {
+                            // Update the header
+                            psRegion *biassec = psListGetAndIncrement(biassecsIter);
+                            psString biassecString = psRegionToString(*biassec);
+                            psMetadataAdd(header, PS_LIST_TAIL, keyword, PS_DATA_STRING | PS_META_REPLACE,
+                                          "Bias section", biassecString);
+                            psFree(biassecString);
+                        }
+                        psFree(keywordsIter);
+                        psFree(biassecsIter);
+                    }
+                }
+                psFree(keywords);
+            } else {
+                psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
+            }
+        }
+    }
+
+    // CELL.XBIN and CELL.YBIN
+    {
+        psMetadataItem *xBinItem = psMetadataLookup(cell->concepts, "CELL.XBIN"); // Binning factor in x
+        psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y
+
+        // If these are specified by the header, we need to check to see if it's the same header keyword
+        const psMetadata *camera = fpa->camera;
+        psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
+        psString xKeyword = psMetadataLookupStr(NULL, translation, "CELL.XBIN");
+        psString yKeyword = psMetadataLookupStr(NULL, translation, "CELL.YBIN");
+        if (strlen(xKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0)
+        {
+            setConceptInHeader(fpa, chip, cell, xBinItem);
+            xBinItem = NULL;
+        }
+        if (strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0)
+        {
+            setConceptInHeader(fpa, chip, cell, yBinItem);
+            yBinItem = NULL;
+        }
+        if (strlen(xKeyword) > 0 && strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) == 0)
+        {
+            psString binString = psStringCopy("");
+            psStringAppend(&binString, "%d,%d", xBinItem->data.S32, yBinItem->data.S32);
+            psMetadataItem *binItem = psMetadataItemAllocStr(xKeyword, "Binning factor in x and y",
+                                      binString);
+            psFree(binString);
+            psMetadata *header = NULL; // The FITS header
+            if (cell->hdu) {
+                header = cell->hdu->header;
+            } else if (chip->hdu) {
+                header = chip->hdu->header;
+            } else if (fpa->hdu) {
+                header = fpa->hdu->header;
+            }
+            if (! header) {
+                psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+            } else {
+                psMetadataAddItem(header, binItem, PS_LIST_TAIL, PS_META_REPLACE);
+                xBinItem = NULL;
+                yBinItem = NULL;
+            }
+            psFree(binItem);
+        }
+
+        // Do it in the usual way if we have to
+        if (xBinItem)
+        {
+            setConceptItem(fpa, chip, cell, db, xBinItem);
+        }
+        if (yBinItem)
+        {
+            setConceptItem(fpa, chip, cell, db, yBinItem);
+        }
+    }
+
+    // CELL.TIMESYS
+    {
+        psMetadataItem *sysItem = psMetadataLookup(cell->concepts, "CELL.TIMESYS");
+        psString sys = NULL;            // String to store
+        switch (sysItem->data.S32)
+        {
+        case PS_TIME_TAI:
+            sys = psStringCopy("TAI");
+            break;
+        case PS_TIME_UTC:
+            sys = psStringCopy("UTC");
+            break;
+        case PS_TIME_UT1:
+            sys = psStringCopy("UT1");
+            break;
+        case PS_TIME_TT:
+            sys = psStringCopy("TT");
+            break;
+        default:
+            sys = psStringCopy("Unknown");
+        }
+        psMetadataItem *newItem = psMetadataItemAllocStr("CELL.TIMESYS", "Time system", sys);
+        setConceptItem(fpa, chip, cell, db, newItem);
+        psFree(newItem);
+        psFree(sys);
+    }
+
+    // CELL.TIME
+    {
+        psMetadataItem *timeItem = psMetadataLookup(cell->concepts, "CELL.TIME");
+        psTime *time = timeItem->data.V; // The time
+        psString dateTimeString = psTimeToISO(time); // String representation
+
+        psMetadata *formats = psMetadataLookupMD(NULL, fpa->camera, "FORMATS");
+        psString format = psMetadataLookupStr(NULL, formats, "CELL.TIME");
+
+        if (strlen(format) == 0)
+        {
+            // No format specified --> do it in the usual way (maybe it's a DB lookup)
+            psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                          dateTimeString);
+            setConceptItem(fpa, chip, cell, db, newTimeItem);
+            psFree(newTimeItem);
+        } else
+        {
+            if (strcasecmp(format, "ISO") == 0) {
+                // dateTimeString contains an ISO time
+                psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                              dateTimeString);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            } else if (strstr(format, "SEPARATE")) {
+                // We're working with two separate headers
+                psMetadata *header = NULL; // The FITS header
+                if (cell->hdu) {
+                    header = cell->hdu->header;
+                } else if (chip->hdu) {
+                    header = chip->hdu->header;
+                } else if (fpa->hdu) {
+                    header = fpa->hdu->header;
+                }
+                if (! header) {
+                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                } else {
+                    // Get the headers
+                    const psMetadata *camera = fpa->camera;
+                    psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
+                    psString keywords = psMetadataLookupStr(NULL, translation, "CELL.TIME");
+                    psList *dateTimeKeywords = psStringSplit(keywords, " ,;");
+                    psString dateKeyword = psListGet(dateTimeKeywords, PS_LIST_HEAD);
+                    psString timeKeyword = psListGet(dateTimeKeywords, PS_LIST_TAIL);
+
+                    psList *dateTime = psStringSplit(dateTimeString, " T"); // Find the middle T
+                    psString dateString = psListGet(dateTime, PS_LIST_HEAD);
+                    psString timeString = psListGet(dateTime, PS_LIST_TAIL);
+
+                    // XXX: Couldn't be bothered doing these right now
+                    if (strstr(format, "PRE2000")) {
+                        psError(PS_ERR_IO, true, "Don't you realise it's the twenty-first century?\n");
+                    }
+                    if (strstr(format, "BACKWARDS")) {
+                        psError(PS_ERR_IO, true, "You want it BACKWARDS?  Not right now, thanks.\n");
+                    }
+
+                    psMetadataItem *dateItem = psMetadataItemAllocStr(dateKeyword, "Date of observation",
+                                               dateString);
+                    psMetadataItem *timeItem = psMetadataItemAllocStr(timeKeyword, "Time of observation",
+                                               timeString);
+                    psMetadataAddItem(header, dateItem, PS_LIST_TAIL, PS_META_REPLACE);
+                    psMetadataAddItem(header, timeItem, PS_LIST_TAIL, PS_META_REPLACE);
+
+                    psFree(dateTimeKeywords);
+                    psFree(dateTime);
+                    psFree(dateItem);
+                    psFree(timeItem);
+                }
+            } else if (strcasecmp(format, "MJD") == 0) {
+                double mjd = psTimeToMJD(time);
+                psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "MJD of observation", mjd);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            } else if (strcasecmp(format, "JD") == 0) {
+                double jd = psTimeToMJD(time);
+                psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            } else {
+                psError(PS_ERR_IO, true, "Not sure how to outgest CELL.TIME (%s) --- trying "
+                        "ISO\n", dateTimeString);
+                psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                              dateTimeString);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            }
+        }
+
+        psFree(dateTimeString);
+    }
+
+    // Remove corrective
+    {
+        const psMetadata *camera = fpa->camera;
+        bool mdok = false;              // Result of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats)
+        {
+            psString format = psMetadataLookupStr(&mdok, formats, "CELL.X0");
+            if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+                psMetadataItem *cellx0 = psMetadataLookup(cell->concepts, "CELL.X0");
+                cellx0->data.S32 += 1;
+            }
+            format = psMetadataLookupStr(&mdok, formats, "CELL.Y0");
+            if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+                psMetadataItem *celly0 = psMetadataLookup(cell->concepts, "CELL.Y0");
+                celly0->data.S32 += 1;
+            }
+        }
+    }
+    // CELL.X0
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.X0");
+    // CELL.Y0
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.Y0");
+
+    // Pau.
+}
+
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsSet.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsSet.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAConceptsSet.h	(revision 6448)
@@ -0,0 +1,20 @@
+#ifndef PM_FPA_CONCEPTS_SET_H
+#define PM_FPA_CONCEPTS_SET_H
+
+#include "pslib.h"
+#include "pmAstrometry.h"
+
+void pmFPAOutgestConcepts(pmFPA *fpa,   // The FPA
+                          psDB *db      // DB handle
+                         );
+void pmChipOutgestConcepts(pmChip *chip,        // The chip
+                           psDB *db     // DB handle
+                          );
+void pmCellOutgestConcepts(pmCell *cell, // The cell
+                           psDB *db     // DB handle
+                          );
+
+
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAConstruct.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAConstruct.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAConstruct.c	(revision 6448)
@@ -0,0 +1,324 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmFPAConstruct.h"
+#include "psAdditionals.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read data for a particular cell from the camera configuration
+static psMetadata *getCellData(const psMetadata *camera, // The camera configuration
+                               const char *cellName // The name of the cell
+                              )
+{
+    bool status = true;                 // Result of MD lookup
+    psMetadata *cells = psMetadataLookupMD(&status, camera, "CELLS"); // The CELLS
+    if (! status) {
+        psError(PS_ERR_IO, false, "Unable to determine CELLS of camera.\n");
+        return NULL;
+    }
+
+    psMetadata *cellData = psMetadataLookupMD(&status, cells, cellName); // The data for the particular cell
+    if (! status) {
+        psError(PS_ERR_IO, false, "Unable to find specs for cell %s: ignored\n", cellName);
+    }
+
+    #if 0
+    // Need to create a new instance, so that each cell can work with its own
+    psMetadata *copy = psMetadataAlloc();
+    psMetadataIterator *iter = psMetadataIteratorAlloc(cellData, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item = NULL;        // Item from iteration
+    while (item = psMetadataGetAndIncrement(iter)) {
+        if (item->type == PS_DATA_METADATA_MULTI || item->type == PS_DATA_METADATA) {
+            psLogMsg(__func__, PS_LOG_WARN, "PS_DATA_METADATA_MULTI and PS_DATA_METADATA are not supported "
+                     "in a cell definition --- %s ignored.\n", item->name);
+            continue;
+        }
+        if (! psMetadataAdd(copy, PS_LIST_TAIL, item->name, item->type, item->comment, item->data.V)) {
+            psAbort(__func__, "Should never reach here!\n");
+        }
+    }
+    psFree(iter);
+
+    return copy;
+    #else
+
+    return cellData;
+    #endif
+
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmFPA *pmFPAConstruct(const psMetadata *camera // The camera configuration
+                     )
+{
+    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out
+
+    bool mdStatus = true;               // Status from metadata lookups
+    const char *phuType = psMetadataLookupStr(&mdStatus, camera, "PHU"); // What is the PHU?
+    const char *extType = psMetadataLookupStr(&mdStatus, camera, "EXTENSIONS"); // What's in the extns?
+
+    if (strcasecmp(phuType, "FPA") == 0) {
+        // The FITS file contains an entire FPA
+
+        psMetadata *contents = psMetadataLookupMD(&mdStatus, camera, "CONTENTS"); // The CONTENTS
+        if (! mdStatus) {
+            psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
+            psFree(fpa);
+            return NULL;
+        }
+
+        // Set up iteration over the contents
+        psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
+        psMetadataItem *contentItem = NULL; // Item from the metadata
+
+        if (strcasecmp(extType, "CHIP") == 0) {
+            // Extensions are chips; Content contains a list of cells
+            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
+                psString extName = contentItem->name; // The name of the extension
+                pmChip *chip = pmChipAlloc(fpa, extName); // The chip
+                chip->hdu = p_pmHDUAlloc(extName); // Prepare chip to receive FITS data
+                if (contentItem->type != PS_DATA_STRING) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+                             contentItem->type);
+                    continue;
+                }
+
+                const char *content = contentItem->data.V; // The content of the extension
+                psTrace(__func__, 7, "Content of %s is: %s\n", extName, content);
+                psList *cellNames = psStringSplit(content, " ,"); // A list of the component cells
+                psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, NULL);
+                char *cellName = NULL; // The name of a cell
+                while ((cellName = psListGetAndIncrement(cellNamesIter))) {
+                    // Get the cell data
+                    psMetadata *cellData = getCellData(camera, cellName);
+                    pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
+                    psFree(cell);       // Drop reference
+                    //                    psFree(cellData);   // Drop reference
+                }
+                psFree(cellNamesIter);
+                psFree(cellNames);
+                psFree(chip);           // Drop reference
+            }
+
+        } else if (strcasecmp(extType, "CELL") == 0) {
+            // Extensions are cells; Content contains a chip name and cell type
+            psMetadata *chips = psMetadataAlloc(); // Given a chip name, holds the chip number
+            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
+                psString extName = contentItem->name; // The name of the extension
+                psTrace(__func__, 1, "Getting %s....\n", extName);
+
+                if (contentItem->type != PS_DATA_STRING) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+                             contentItem->type);
+                    continue;
+                }
+                const char *content = contentItem->data.V; // The content of the extension
+                psList *contents = psStringSplit(content, ": "); // Split the name from the type
+                if (contents->n != 2) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unable to read contents of %s: ignored.\n", extName);
+                } else {
+                    psString chipName = psListGet(contents, 0); // The name of the chip
+                    psString cellType = psListGet(contents, 1); // The type of cell
+                    psTrace(__func__, 7, "Extension is cell of type %s, from chip %s\n", cellType,
+                            chipName);
+
+                    psMetadataItem *chipItem = psMetadataLookup(chips, chipName); // Item containing the chip
+                    pmChip *chip = NULL; // The chip
+                    if (! chipItem) {
+                        chip = pmChipAlloc(fpa, chipName);
+                        psMetadataAdd(chips, PS_LIST_TAIL, chipName, PS_DATA_UNKNOWN, "", chip);
+                    } else {
+                        chip = psMemIncrRefCounter(chipItem->data.V);
+                    }
+                    // The cell
+                    psMetadata *cellData = getCellData(camera, cellType);
+                    pmCell *cell = pmCellAlloc(chip, cellData, extName); // The cell
+                    //                  psFree(cellData); // Drop reference
+                    cell->hdu = p_pmHDUAlloc(extName); // Prepare cell to receive FITS data
+
+                    psFree(cell);       // Drop reference
+                    psFree(chip);       // Drop reference
+                }
+                psFree(contents);
+            }
+            psFree(chips);
+
+        } else if (strcasecmp(extType, "NONE") == 0) {
+            // No extensions; Content contains metadata, each entry is a chip with its component cells
+            fpa->hdu = p_pmHDUAlloc("PHU"); // Prepare FPA to receive FITS data
+            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
+                psString chipName = contentItem->name; // The name of the chip
+
+                if (contentItem->type != PS_DATA_STRING) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+                             contentItem->type);
+                    continue;
+                }
+                psString content = contentItem->data.V; // The content of the extension
+                psTrace(__func__, 5, "Component cells are: %s\n", content);
+                pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+                psList *cellNames = psStringSplit(content, ", "); // Split the list of cells
+                psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false);
+                char *cellName = NULL; // Name of the cell
+                while ((cellName = psListGetAndIncrement(cellNamesIter))) {
+                    psTrace(__func__, 7, "Processing cell %s....\n", cellName);
+                    psMetadata *cellData = getCellData(camera, cellName);
+                    pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
+                    psFree(cell);       // Drop reference
+                    //                  psFree(cellData);   // Drop reference
+                }
+                psFree(cellNamesIter);
+                psFree(chip);           // Drop reference
+            }
+
+        } else {
+            psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is not CHIP, CELL or NONE.\n");
+            psFree(fpa);
+            return NULL;
+        } // Type of extension
+
+        psFree(contentsIter);
+
+    } else if (strcasecmp(phuType, "CHIP") == 0) {
+        // The FITS file contains a single chip only
+        psString chipName = psStringCopy("CHIP"); // Name for chip
+        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+
+        if (strcasecmp(extType, "NONE") == 0) {
+            // There are no extensions --- only the PHU
+            chip->hdu = p_pmHDUAlloc("PHU");
+
+            const char *contents = psMetadataLookupStr(&mdStatus, camera, "CONTENTS");
+            if (! mdStatus) {
+                psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
+                psFree(fpa);
+                return NULL;
+            }
+            psList *cellNames = psStringSplit(contents, " ,"); // Names of cells
+            psListIterator *cellIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
+            psString cellName = NULL;
+            while ((cellName = psListGetAndIncrement(cellIter))) {
+                psMetadata *cellData = getCellData(camera, cellName);
+                pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
+                psFree(cell);
+            }
+            psFree(cellIter);
+            psFree(cellNames);
+
+        } else if (strcasecmp(extType, "CELL") == 0) {
+            // Extensions are cells
+            psMetadata *contents = psMetadataLookupMD(&mdStatus, camera, "CONTENTS"); // The CONTENTS
+            if (! mdStatus) {
+                psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
+                psFree(fpa);
+                return NULL;
+            }
+
+            if (strcasecmp(extType, "CELL") != 0) {
+                psLogMsg(__func__, PS_LOG_WARN, "EXTENSIONS in camera definition is %s, but PHU is CHIP.\n"
+                         "EXTENSIONS assumed to be CELL.\n", extType);
+            }
+
+            // Iterate through the contents
+            psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
+            psMetadataItem *contentItem = NULL; // Item from metadata
+            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
+                psString extName = contentItem->name; // The name of the extension
+                // Content is a cell type
+                if (contentItem->type != PS_DATA_STRING) {
+                    psLogMsg(__func__, PS_LOG_WARN,
+                             "CONTENT metadata for extension %s is not of type string, but %x --- ignored\n",
+                             extName, contentItem->type);
+                    continue;
+                }
+                const char *cellType = contentItem->data.V; // The type of cell
+                psTrace(__func__, 5, "Cell type is %s\n", cellType);
+                psMetadata *cellData = getCellData(camera, cellType);
+                pmCell *cell = pmCellAlloc(chip, cellData, extName); // The cell
+                //              psFree(cellData);
+                cell->hdu = p_pmHDUAlloc(extName); // Prepare cell to receive FITS data
+
+                psFree(cell);
+            } // Iterating through contents
+            psFree(contentsIter);
+
+        } else {
+            psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is neither CELL or NONE.\n");
+            psFree(fpa);
+            return NULL;
+        }
+        psFree(chipName);
+        psFree(chip);                   // Drop reference
+
+    } else {
+        psError(PS_ERR_IO, true,
+                "The PHU type specified in the camera configuration (%s) is not FPA or CHIP.\n",
+                phuType);
+        psFree(fpa);
+        return NULL;
+    }
+
+    return fpa;
+}
+
+// Print out the focal plane structure
+void pmFPAPrint(pmFPA *fpa              // FPA to print
+               )
+{
+    psTrace(__func__, 1, "FPA:\n");
+    if (fpa->hdu) {
+        psTrace(__func__, 2, "---> FPA is extension %s.\n", fpa->hdu->extname);
+        if (! fpa->hdu->images) {
+            psTrace(__func__, 2, "---> NO PIXELS for extension %s\n", fpa->hdu->extname);
+        }
+    }
+    psMetadataPrint(fpa->concepts, 2);
+
+    psArray *chips = fpa->chips;        // Array of chips
+    // Iterate over the FPA
+    for (int i = 0; i < chips->n; i++) {
+        psTrace(__func__, 3, "Chip: %d\n", i);
+        pmChip *chip = chips->data[i]; // The chip
+        if (chip->hdu) {
+            psTrace(__func__, 4, "---> Chip is extension %s.\n", chip->hdu->extname);
+            if (! chip->hdu->images) {
+                psTrace(__func__, 4, "---> NO PIXELS for extension %s\n", chip->hdu->extname);
+            }
+        }
+        psMetadataPrint(chip->concepts, 4);
+
+        // Iterate over the chip
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            psTrace(__func__, 5, "Cell: %d\n", j);
+            pmCell *cell = cells->data[j]; // The cell
+            if (cell->hdu) {
+                psTrace(__func__, 6, "---> Cell is extension %s.\n", cell->hdu->extname);
+                if (! cell->hdu->images) {
+                    psTrace(__func__, 6, "---> NO PIXELS for extension %s\n", cell->hdu->extname);
+                }
+            }
+            psMetadataPrint(cell->concepts, 6);
+
+            psTrace(__func__, 7, "Readouts:\n");
+            psArray *readouts = cell->readouts; // Array of readouts
+            for (int k = 0; k < readouts->n; k++) {
+                pmReadout *readout = readouts->data[k]; // The readout
+                psImage *image = readout->image; // The image
+                psTrace(__func__, 8, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 +
+                        image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
+                        image->numRows);
+            } // Iterating over cell
+        } // Iterating over chip
+    } // Iterating over FPA
+
+}
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAConstruct.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAConstruct.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAConstruct.h	(revision 6448)
@@ -0,0 +1,16 @@
+#ifndef PM_FPA_CONSTRUCT_H
+#define PM_FPA_CONSTRUCT_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Read the contents of a FITS file (format specified by the camera configuration) into memory
+pmFPA *pmFPAConstruct(const psMetadata *camera // The camera configuration
+                     );
+
+// Print out the FPA
+void pmFPAPrint(pmFPA *fpa              // FPA to print
+               );
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPARead.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPARead.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPARead.c	(revision 6448)
@@ -0,0 +1,657 @@
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmFPARead.h"
+#include "pmConcepts.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// Read a FITS extension into a chip
+static bool readExtension(p_pmHDU *hdu, // Pixel data into which to read
+                          psFits *fits  // The FITS file from which to read
+                         )
+{
+    const char *extName = hdu->extname; // Extension name
+
+    psTrace(__func__, 7, "Moving to extension %s...\n", extName);
+    if (strncmp(extName, "PHU", 3) == 0) {
+        if (!psFitsMoveExtNum(fits, 0, false)) {
+            psError(PS_ERR_IO, false, "Unable to find PHU in FITS file!\n");
+            return false;
+        }
+    } else if (! psFitsMoveExtName(fits, extName)) {
+        psError(PS_ERR_IO, false, "Unable to find extension %s in FITS file!\n", extName);
+        return false;
+    }
+    psTrace(__func__, 7, "Reading header....\n");
+    psMetadata *header = psFitsReadHeader(NULL, fits); // Header
+    if (! header) {
+        psError(PS_ERR_IO, false, "Unable to read FITS header!\n");
+        return false;
+    }
+
+    psTrace(__func__, 7, "Checking NAXIS....\n");
+    bool mdStatus = false;
+    int nAxis = psMetadataLookupS32(&mdStatus, header, "NAXIS");
+    if (!mdStatus) {
+        psLogMsg(__func__, PS_LOG_WARN, "There is no NAXIS keyword in the FITS header of extension %s!\n",
+                 extName);
+    }
+    if (nAxis != 2 && nAxis != 3) {
+        psLogMsg(__func__, PS_LOG_WARN, "Image is not 2- or 3-dimensional --- reading into a single image "
+                 "anyway.\n");
+    }
+    psTrace(__func__, 9, "NAXIS = %d\n", nAxis);
+
+    int numPlanes = 1;                  // Number of planes
+    if (nAxis == 3) {
+        numPlanes = psMetadataLookupS32(&mdStatus, header, "NAXIS3");
+        if (!mdStatus) {
+            psError(PS_ERR_IO, false, "Unable to read NAXIS3 for 3-dimensional image!\n");
+            return false;
+        }
+    }
+    psRegion region = {0, 0, 0, 0};     // Region to read is everything
+
+    // Read each plane into the array
+    psTrace(__func__, 7, "Reading %d planes into array....\n", numPlanes);
+    psArray *pixels = psArrayAlloc(numPlanes); // Array of images
+    for (int i = 0; i < numPlanes; i++) {
+        psTrace(__func__, 9, "Reading plane %d\n", i);
+        psMemCheckCorruption(true);
+        psImage *image = psFitsReadImage(NULL, fits, region, i);
+
+        // XXX: Type conversion here to support the modules, which don't have multiple type support yet
+        if (image->type.type != PS_TYPE_F32) {
+            pixels->data[i] = psImageCopy(NULL, image, PS_TYPE_F32);
+
+            // XXX: Temporary fix for writing images, until psFits gets cleaned up
+            psMetadataItem *bitpixItem = psMetadataLookup(header, "BITPIX");
+            bitpixItem->data.S32 = -32;
+            psMetadataRemove(header, 0, "BZERO");
+            psMetadataRemove(header, 0, "BSCALE");
+
+            psFree(image);
+        } else {
+            pixels->data[i] = image;
+        }
+        psTrace(__func__, 10, "Done\n");
+        if (! pixels->data[i]) {
+            psError(PS_ERR_IO, false, "Unable to read image for extension %s, plane %d\n", extName, i);
+            return false;
+        }
+    }
+
+    // Update the HDU with the new data
+    hdu->images = pixels;
+    hdu->header = header;
+
+    // Mask and weight not set yet
+    hdu->weights = NULL;
+    hdu->masks = NULL;
+
+    return true;
+}
+
+// Portion out an image into the cell
+static bool generateReadouts(pmCell *cell, // The cell that gets its bits
+                             p_pmHDU *hdu // Pixel data, containing image, mask, weights
+                            )
+{
+    psArray *images = hdu->images;      // Array of images (each of which is a readout)
+
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+    psList *biassecs = psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC");
+
+    // Iterate over each of the image planes
+    for (int i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The i-th plane
+        pmReadout *readout = pmReadoutAlloc(cell);
+
+        readout->image = psMemIncrRefCounter(psImageSubset(image, *trimsec)); // The image corresponding to the trim region
+
+        // Get the list of overscans
+        psListIterator *iter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;       // A BIASSEC region from the list
+        while ((biassec = psListGetAndIncrement(iter))) {
+            psImage *overscan = psMemIncrRefCounter(psImageSubset(image, *biassec));
+            psListAdd(readout->bias, PS_LIST_TAIL, overscan);
+            psFree(overscan);
+        }
+        psFree(iter);
+
+        readout->mask = NULL;
+        readout->weight = NULL;
+
+        psFree(readout);                // Drop reference
+    }
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmFPARead(pmFPA *fpa,              // FPA to read into
+               psFits *fits,            // FITS file from which to read
+               psMetadata *phu,         // Primary header
+               psDB *db                 // Database handle, for concept ingest
+              )
+{
+    p_pmHDU *hdu = NULL;        // Pixel data from FITS file
+
+    // Read the PHU, if required
+    if (! phu) {
+        if (! psFitsMoveExtNum(fits, 0, false)) {
+            psError(PS_ERR_IO, false, "Unable to find PHU in FITS file!\n");
+            return false;
+        }
+        psTrace(__func__, 7, "Reading PHU....\n");
+        fpa->phu = psFitsReadHeader(NULL, fits); // Primary header
+    } else if (! fpa->phu) {
+        fpa->phu = psMemIncrRefCounter(phu);
+    }
+
+    // Read in....
+    psTrace(__func__, 1, "Reading FPA...\n");
+    if (fpa->hdu) {
+        hdu = fpa->hdu;
+        psTrace(__func__, 3, "Reading pixels from extension %s into FPA.\n", hdu->extname);
+        if (! readExtension(hdu, fits)) {
+            psError(PS_ERR_IO, false, "Unable to read pixels from extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    pmConceptsReadFPA(fpa, db);
+
+    psArray *chips = fpa->chips;        // Array of chips
+    // Iterate over the FPA
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i]; // The chip
+
+        // Only read chips marked to "read"
+        if (! chip || ! chip->process || chip->exists) {
+            psTrace(__func__, 2, "Ignoring chip %d...\n", i);
+            continue;
+        }
+        psTrace(__func__, 2, "Reading in chip %d...\n", i);
+
+        if (chip->hdu) {
+            hdu = chip->hdu;
+            psTrace(__func__, 3, "Reading pixels from extension %s into chip %d.\n", hdu->extname, i);
+            if (! readExtension(hdu, fits)) {
+                psError(PS_ERR_IO, false, "Unable to read pixels from extension %s\n", hdu->extname);
+                return false;
+            }
+        }
+
+        pmConceptsReadChip(chip, db);
+
+        // Iterate over the chip
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // The cell
+
+            // Only read cells marked to "read"
+            if (!cell || ! cell->process || cell->exists) {
+                psTrace(__func__, 3, "Ignoring chip %d...\n", i);
+                continue;
+            }
+            psTrace(__func__, 3, "Reading in cell %d...\n", j);
+
+            if (cell->hdu) {
+                hdu = cell->hdu;
+                psTrace(__func__, 5, "Reading pixels from extension %s into cell %d.\n", hdu->extname,
+                        j);
+                if (! readExtension(hdu, fits)) {
+                    psError(PS_ERR_IO, false, "Unable to read pixels from extension %s\n",
+                            hdu->extname);
+                    return false;
+                }
+            }
+            psTrace(__func__, 5, "Reading concepts for cell %d...\n", j);
+            pmConceptsReadCell(cell, db);
+
+            psTrace(__func__, 5, "Allocating readouts for chip %d cell %d...\n", i, j);
+            generateReadouts(cell, hdu);
+
+            cell->exists = true;
+        }
+        chip->exists = true;
+    }
+
+    psTrace(__func__, 1, "Done reading FPA...\n");
+
+    return true;
+}
+
+// Translate a name from the configuration file, containing something like "%a_%d" to a real value
+psString p_pmFPATranslateName(const psString name, // The name to translate
+                              const pmCell *cell // The cell for which to translate
+                             )
+{
+    // %a is the FPA.NAME
+    // %b is the CHIP.NAME
+    // %c is the CELL.NAME
+    // %d is the chip number
+    // %e if the cell number
+    // %f is the extension name
+
+    psString translation = psMemIncrRefCounter(name); // The translated string
+    char *temp = NULL;                  // Temporary string
+
+    pmChip *chip = cell->parent;        // Chip of interest
+    pmFPA *fpa = chip->parent;          // FPA of interest
+
+    // FPA.NAME
+    if ((temp = strstr(translation, "%a"))) {
+        // This is not particularly friendly to the memory allocation, but the cache should make it OK,
+        // and there's not much of it anyway...
+        psFree(translation);
+        translation = psStringNCopy(name, strlen(name) - strlen(temp)); // Copy first part of string
+        psString fpaName = psMetadataLookupStr(NULL, fpa->concepts, "FPA.NAME"); // The value of FPA.NAME
+        psStringAppend(&translation, "%s%s", fpaName, temp + 2);
+        // So "translation" now contains the first part, the replaced string, and the last part.
+    }
+
+    // CHIP.NAME
+    if ((temp = strstr(translation, "%b"))) {
+        // This is not particularly friendly to the memory allocation, but the cache should make it OK,
+        // and there's not much of it anyway...
+        psFree(translation);
+        translation = psStringNCopy(name, strlen(name) - strlen(temp)); // Copy first part of string
+        psString chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // CHIP.NAME's value
+        psStringAppend(&translation, "%s%s", chipName, temp + 2);
+        // So "translation" now contains the first part, the replaced string, and the last part.
+    }
+
+    // CELL.NAME
+    if ((temp = strstr(translation, "%c"))) {
+        // This is not particularly friendly to the memory allocation, but the cache should make it OK,
+        // and there's not much of it anyway...
+        psFree(translation);
+        translation = psStringNCopy(name, strlen(name) - strlen(temp)); // Copy first part of string
+        psString cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // CELL.NAME's value
+        psStringAppend(&translation, "%s%s", cellName, temp + 2);
+        // So "translation" now contains the first part, the replaced string, and the last part.
+    }
+
+    // Chip number
+    if ((temp = strstr(translation, "%d"))) {
+        // This is not particularly friendly to the memory allocation, but the cache should make it OK,
+        // and there's not much of it anyway...
+        psFree(translation);
+        translation = psStringNCopy(name, strlen(name) - strlen(temp)); // Copy first part of string
+        // Search for the pointer to get the chip number
+        int chipNum = -1;
+        psArray *chips = fpa->chips;    // The array of chips
+        for (int i = 0; i < chips->n && chipNum < 0; i++) {
+            if (chips->data[i] == chip) {
+                chipNum = i;
+            }
+        }
+        if (chipNum < 0) {
+            psError(PS_ERR_IO, true, "Unable to find chip to get name: %s\n", name);
+            // Try to muddle on by leaving the number out
+            psStringAppend(&translation, "%s", temp + 2);
+        } else {
+            psStringAppend(&translation, "%d%s", chipNum, temp + 2);
+        }
+        // So "translation" now contains the first part, the replaced string, and the last part.
+    }
+
+    // Cell number
+    if ((temp = strstr(translation, "%e"))) {
+        // This is not particularly friendly to the memory allocation, but the cache should make it OK,
+        // and there's not much of it anyway...
+        psFree(translation);
+        translation = psStringNCopy(name, strlen(name) - strlen(temp)); // Copy first part of string
+        // Search for the pointer to get the cell number
+        int cellNum = -1;
+        psArray *cells = chip->cells;   // The array of cells
+        for (int i = 0; i < cells->n && cellNum < 0; i++) {
+            if (cells->data[i] == cell) {
+                cellNum = i;
+            }
+        }
+        if (cellNum < 0) {
+            psError(PS_ERR_IO, true, "Unable to find cell to get name: %s\n", name);
+            // Try to muddle on by leaving the number out
+            psStringAppend(&translation, "%s", temp + 2);
+        } else {
+            psStringAppend(&translation, "%d%s", cellNum, temp + 2);
+        }
+        // So "translation" now contains the first part, the replaced string, and the last part.
+    }
+
+    // Extension name
+    if ((temp = strstr(translation, "%f"))) {
+        // This is not particularly friendly to the memory allocation, but the cache should make it OK,
+        // and there's not much of it anyway...
+        psFree(translation);
+        translation = psStringNCopy(name, strlen(name) - strlen(temp)); // Copy first part of string
+        const char *extname = NULL;
+        if (cell->hdu) {
+            extname = cell->hdu->extname;
+        } else if (chip->hdu) {
+            extname = chip->hdu->extname;
+        } else if (fpa->hdu) {
+            extname = fpa->hdu->extname;
+        }
+        psStringAppend(&translation, "%s%s", extname, temp + 2);
+        // So "translation" now contains the first part, the replaced string, and the last part.
+    }
+
+    return translation;
+}
+
+// Get filename and extension out of "somefile.fits:ext"
+psString p_pmFPATranslateFileExt(psString *extName, // Extension name, to be returned
+                                 const psString name, // The string to be translated and then parsed
+                                 const pmCell *cell // The cell
+                                )
+{
+    psString filenameExt = p_pmFPATranslateName(name, cell);
+    const char *colon = strchr(filenameExt, ':'); // Pointer to a colon in the filename-extn
+    psString filename = NULL;           // The filename
+    if (extName && *extName) {
+        psFree(*extName);
+    }
+    if (extName) {
+        *extName = NULL;
+    }
+
+    if (colon) {
+        filename = psStringNCopy(filenameExt, strlen(filenameExt) - strlen(colon));
+        if (strlen(colon) > 1) {
+            if (extName) {
+                *extName = psStringCopy(colon + 1);
+            }
+        }
+    } else {
+        filename = psMemIncrRefCounter(filenameExt);
+    }
+
+    return filename;
+}
+
+// Read a mask into the FPA
+bool pmFPAReadMask(pmFPA *fpa,          // FPA to read into
+                   psFits *source       // Source FITS file (for the original data)
+                  )
+{
+    const psMetadata *camera = fpa->camera; // Camera configuration for FPA
+    bool mdok = false;                  // Status of MD lookup
+
+    // Get the required information from the camera configuration
+    psMetadata *supps = psMetadataLookupMD(&mdok, camera, "SUPPLEMENTARY"); // Rules for supplementary data
+    if (! mdok || ! supps) {
+        psError(PS_ERR_IO, false, "Unable to find SUPPLEMENTARY in camera configuration!\n");
+        return false;
+    }
+    psString sourceType = psMetadataLookupStr(&mdok, supps, "MASK.SOURCE"); // Type of source: EXT | FILE
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find MASK.SOURCE in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+    psString name = psMetadataLookupStr(&mdok, supps, "MASK.NAME"); // Name of mask
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find MASK.NAME in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+
+    // Go through the FPA to each cell/readout to get the mask
+    p_pmHDU *hdu = fpa->hdu;            // The HDU into which we will read the mask
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int chipNum = 0; chipNum < chips->n; chipNum++) {
+        pmChip *chip = chips->data[chipNum]; // The current chip of interest
+        if (chip->process && !chip->exists) {
+            if (chip->hdu) {
+                hdu = chip->hdu;
+            }
+            psArray *cells = chip->cells;       // Array of cells
+            for (int cellNum = 0; cellNum < cells->n; cellNum++) {
+                pmCell *cell = cells->data[cellNum]; // The current cell of interest
+                if (cell->process && !cell->exists) {
+                    if (cell->hdu) {
+                        hdu = cell->hdu;
+                    }
+
+                    // Now, need to find out where to get the pixels
+                    psFits *maskSource = psMemIncrRefCounter(source); // Source of mask image
+                    if (strcasecmp(sourceType, "FILE") == 0) {
+                        // Source is a file (with optional extension, e.g., "myMaskFile.fits:thisExt"
+                        psString extname = NULL; // Extension name
+                        psString filename = p_pmFPATranslateFileExt(&extname, name, cell); // The filename
+
+                        psFree(maskSource);
+                        maskSource = psFitsOpen(filename, "r");
+                        if (extname) {
+                            if (! psFitsMoveExtName(maskSource, extname)) {
+                                psLogMsg(__func__, PS_LOG_WARN, "Unable to find extension %s to read mask.\n",
+                                         extname);
+                                return false;
+                            }
+                        }
+                        psFree(filename);
+                        psFree(extname);
+                    } else if (strncasecmp(sourceType, "EXT", 3) == 0) {
+                        // Source is an extension in the original file
+                        psString extname = p_pmFPATranslateName(name, cell);
+                        psFitsMoveExtName(maskSource, extname);
+                    }
+
+                    // We've arrived where the pixels are.  Now we need to read them in.
+                    psMetadata *header = psFitsReadHeader(NULL, maskSource); // The header
+                    bool mdStatus = false;
+                    int nAxis = psMetadataLookupS32(&mdStatus, header, "NAXIS");
+                    if (!mdStatus) {
+                        psLogMsg(__func__, PS_LOG_WARN, "There is no NAXIS keyword in the FITS header for "
+                                 "mask (%s)!\n", name);
+                    }
+                    if (nAxis != 2 && nAxis != 3) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Image is not 2- or 3-dimensional --- reading into "
+                                 "a single image anyway.\n");
+                    }
+
+                    int numPlanes = 1;  // Number of planes
+                    if (nAxis == 3) {
+                        numPlanes = psMetadataLookupS32(&mdStatus, header, "NAXIS3");
+                        if (!mdStatus) {
+                            psError(PS_ERR_IO, false, "Unable to read NAXIS3 for 3-dimensional image!\n");
+                            // Try to proceed by taking only the first plane
+                            numPlanes = 1;
+                        }
+                        if (numPlanes != 1 && numPlanes != cell->readouts->n) {
+                            psError(PS_ERR_IO, false, "Number of masks (%d) does not match number of "
+                                    "readouts (%d)\n", numPlanes, cell->readouts->n);
+                            // Try to proceed by taking only the first plane
+                            numPlanes = 1;
+                        }
+                    }
+
+                    hdu->masks = psArrayAlloc(hdu->images->n);
+
+                    // Read each plane into the array
+                    psArray *readouts = cell->readouts; // The array of readouts
+                    for (int i = 0; i < hdu->masks->n; i++) {
+                        psImage *mask = NULL; // The mask to be added
+                        if (i < numPlanes) {
+                            // Read the mask from the file
+                            psTrace(__func__, 9, "Reading plane %d\n", i);
+                            psRegion region = {0, 0, 0, 0};
+                            mask = psFitsReadImage(NULL, maskSource, region, i);
+                        } else {
+                            // One mask in the file is provided for all planes in the original image
+                            psTrace(__func__, 9, "Copying plane 0 into plane %d\n", i);
+                            psImage *original = hdu->masks->data[0];
+                            mask = psImageCopy(NULL, original, original->type.type);
+                        }
+                        hdu->masks->data[0] = mask;
+                        pmReadout *readout = readouts->data[i];
+                        readout->mask = mask;
+                        // Check the dimensions
+                        // XXX: Perhaps this check should be against the CELL.TRIMSEC, not the image size?
+                        if (mask->numCols < readout->image->numCols ||
+                                mask->numRows < readout->image->numRows) {
+                            psError(PS_ERR_IO, false, "Mask size (%dx%d) not compatible with image (%dx%d)\n",
+                                    mask->numCols, mask->numRows, readout->image->numCols,
+                                    readout->image->numRows);
+                            return false;
+                        }
+                    } // Iterating over readouts
+                    psFree(maskSource);
+                } // Valid cells
+            } // Iterating over cells
+        } // Valid chips
+    } // Iterating over chips
+
+    return true;
+}
+
+
+// Read a mask into the FPA
+// This is just a copy of the above pmFPAReadMask, replacing "mask" with "weight" throughout.
+bool pmFPAReadWeight(pmFPA *fpa,        // FPA to read into
+                     psFits *source     // Source FITS file (for the original data)
+                    )
+{
+    const psMetadata *camera = fpa->camera; // Camera configuration for FPA
+    bool mdok = false;                  // Status of MD lookup
+
+    // Get the required information from the camera configuration
+    psMetadata *supps = psMetadataLookupMD(&mdok, camera, "SUPPLEMENTARY"); // Rules for supplementary data
+    if (! mdok || ! supps) {
+        psError(PS_ERR_IO, false, "Unable to find SUPPLEMENTARY in camera configuration!\n");
+        return false;
+    }
+    psString sourceType = psMetadataLookupStr(&mdok, supps, "WEIGHT.SOURCE"); // Type of source: EXT | FILE
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find WEIGHT.SOURCE in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+    psString name = psMetadataLookupStr(&mdok, supps, "WEIGHT.NAME"); // Name of weight
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find WEIGHT.NAME in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+
+    // Go through the FPA to each cell/readout to get the weight
+    p_pmHDU *hdu = fpa->hdu;            // The HDU into which we will read the weight
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int chipNum = 0; chipNum < chips->n; chipNum++) {
+        pmChip *chip = chips->data[chipNum]; // The current chip of interest
+        if (chip->process && !chip->exists) {
+            if (chip->hdu) {
+                hdu = chip->hdu;
+            }
+            psArray *cells = chip->cells;       // Array of cells
+            for (int cellNum = 0; cellNum < cells->n; cellNum++) {
+                pmCell *cell = cells->data[cellNum]; // The current cell of interest
+                if (cell->process && !cell->exists) {
+                    if (cell->hdu) {
+                        hdu = cell->hdu;
+                    }
+
+                    // Now, need to find out where to get the pixels
+                    psFits *weightSource = psMemIncrRefCounter(source); // Source of weight image
+                    if (strcasecmp(sourceType, "FILE") == 0) {
+                        // Source is a file (with optional extension, e.g., "myWeightFile.fits:thisExt"
+                        psString extname = NULL; // Extension name
+                        psString filename = p_pmFPATranslateFileExt(&extname, name, cell); // The filename
+
+                        psFree(weightSource);
+                        weightSource = psFitsOpen(filename, "r");
+                        if (extname) {
+                            if (! psFitsMoveExtName(weightSource, extname)) {
+                                psLogMsg(__func__, PS_LOG_WARN, "Unable to find extension %s to read "
+                                         "weight.\n", extname);
+                                return false;
+                            }
+                        }
+                        psFree(filename);
+                        psFree(extname);
+                    } else if (strncasecmp(sourceType, "EXT", 3) == 0) {
+                        // Source is an extension in the original file
+                        psString extname = p_pmFPATranslateName(name, cell);
+                        psFitsMoveExtName(weightSource, extname);
+                    }
+
+                    // We've arrived where the pixels are.  Now we need to read them in.
+                    psMetadata *header = psFitsReadHeader(NULL, weightSource); // The header
+                    bool mdStatus = false;
+                    int nAxis = psMetadataLookupS32(&mdStatus, header, "NAXIS");
+                    if (!mdStatus) {
+                        psLogMsg(__func__, PS_LOG_WARN, "There is no NAXIS keyword in the FITS header for "
+                                 "weight (%s)!\n", name);
+                    }
+                    if (nAxis != 2 && nAxis != 3) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Image is not 2- or 3-dimensional --- reading into "
+                                 "a single image anyway.\n");
+                    }
+
+                    int numPlanes = 1;  // Number of planes
+                    if (nAxis == 3) {
+                        numPlanes = psMetadataLookupS32(&mdStatus, header, "NAXIS3");
+                        if (!mdStatus) {
+                            psError(PS_ERR_IO, false, "Unable to read NAXIS3 for 3-dimensional image!\n");
+                            // Try to proceed by taking only the first plane
+                            numPlanes = 1;
+                        }
+                        if (numPlanes != 1 && numPlanes != cell->readouts->n) {
+                            psError(PS_ERR_IO, false, "Number of weights (%d) does not match number of "
+                                    "readouts (%d)\n", numPlanes, cell->readouts->n);
+                            // Try to proceed by taking only the first plane
+                            numPlanes = 1;
+                        }
+                    }
+
+                    hdu->weights = psArrayAlloc(hdu->images->n);
+
+                    // Read each plane into the array
+                    psArray *readouts = cell->readouts; // The array of readouts
+                    for (int i = 0; i < hdu->weights->n; i++) {
+                        psImage *weight = NULL; // The weight to be added
+                        if (i < numPlanes) {
+                            // Read the weight from the file
+                            psTrace(__func__, 9, "Reading plane %d\n", i);
+                            psRegion region = {0, 0, 0, 0};
+                            weight = psFitsReadImage(NULL, weightSource, region, i);
+                        } else {
+                            // One weight in the file is provided for all planes in the original image
+                            psTrace(__func__, 9, "Copying plane 0 into plane %d\n", i);
+                            psImage *original = hdu->weights->data[0];
+                            weight = psImageCopy(NULL, original, original->type.type);
+                        }
+                        hdu->weights->data[0] = weight;
+                        pmReadout *readout = readouts->data[i];
+                        readout->weight = weight;
+                        // Check the dimensions
+                        // XXX: Perhaps this check should be against the CELL.TRIMSEC, not the image size?
+                        if (weight->numCols < readout->image->numCols ||
+                                weight->numRows < readout->image->numRows) {
+                            psError(PS_ERR_IO, false, "Weight size (%dx%d) not compatible with image "
+                                    "(%dx%d)\n", weight->numCols, weight->numRows, readout->image->numCols,
+                                    readout->image->numRows);
+                            return false;
+                        }
+                    } // Iterating over readouts
+                    psFree(weightSource);
+                } // Valid cells
+            } // Iterating over cells
+        } // Valid chips
+    } // Iterating over chips
+
+    return true;
+}
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPARead.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPARead.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPARead.h	(revision 6448)
@@ -0,0 +1,31 @@
+#ifndef PM_FPA_READ_H
+#define PM_FPA_READ_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+bool pmFPARead(pmFPA *fpa,              // FPA to read into
+               psFits *fits,            // FITS file from which to read
+               psMetadata *phu,         // Primary header
+               psDB *db                 // Database handle, for concept ingest
+              );
+
+psString p_pmFPATranslateName(const psString name, // The name to translate
+                              const pmCell *cell // The cell for which to translate
+                             );
+
+psString p_pmFPATranslateFileExt(psString *extName, // Extension name, to be returned
+                                 const psString filenameExt, // The string to parse into filename and ext
+                                 const pmCell *cell // The cell
+                                );
+
+bool pmFPAReadMask(pmFPA *fpa,          // FPA to read into
+                   psFits *source       // Source FITS file (for the original data)
+                  );
+
+bool pmFPAReadWeight(pmFPA *fpa,        // FPA to read into
+                     psFits *source     // Source FITS file (for the original data)
+                    );
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAWrite.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAWrite.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAWrite.c	(revision 6448)
@@ -0,0 +1,259 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmFPARead.h"
+#include "pmConcepts.h"
+
+static bool writeHDU(psFits *fits,      // FITS file to which to write
+                     p_pmHDU *hdu       // Pixel data to write
+                    )
+{
+    bool status = true;                 // Status of write, to return
+    for (int i = 0; i < hdu->images->n; i++) {
+        status &= psFitsWriteImage(fits, hdu->header, hdu->images->data[i], i);
+        // XXX: Insert here the writing on mask and weight images
+    }
+
+    return status;
+}
+
+
+bool pmFPAWrite(psFits *fits,           // FITS file to which to write
+                pmFPA *fpa,             // FPA to write
+                psDB *db                // Database to update
+               )
+{
+    bool status = true;                 // Status of writing, to return
+
+    psTrace(__func__, 1, "Writing FPA...\n");
+
+    psTrace(__func__, 7, "Outgesting FPA concepts...\n");
+    pmConceptsWriteFPA(fpa, db);
+
+    // Write the primary header
+    if (fpa->phu) {
+        status &= psFitsMoveExtNum(fits, 0, false);
+        status &= psFitsWriteHeader(fpa->phu, fits);
+    }
+
+    psArray *chips = fpa->chips;        // Array of component chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The component chip
+        if (chip && chip->exists && chip->process) {
+            psTrace(__func__, 1, "Writing out chip %d...\n", i);
+
+            pmConceptsWriteChip(chip, db);
+
+            psArray *cells = chip->cells;       // Array of component cells
+            for (int j = 0; j < cells->n; j++) {
+                pmCell *cell = cells->data[j]; // The component cell
+                if (cell && cell->exists && cell->process) {
+                    psTrace(__func__, 2, "Writing out cell, %d...\n", j);
+
+                    pmConceptsWriteCell(cell, db);
+
+                    if (cell->hdu && strlen(cell->hdu->extname) > 0) {
+                        status &= writeHDU(fits, cell->hdu);
+                    }
+                }
+            }
+
+            if (chip->hdu && strlen(chip->hdu->extname) > 0) {
+                status &= writeHDU(fits, chip->hdu);
+            }
+        }
+
+    }
+
+    if (fpa->hdu && strlen(fpa->hdu->extname) > 0) {
+        status &= writeHDU(fits, fpa->hdu);
+    }
+
+    psTrace(__func__, 1, "Done writing FPA...\n");
+
+    return status;
+}
+
+
+bool pmFPAWriteMask(pmFPA *fpa,         // FPA containing mask to write
+                    psFits *fits        // FITS file for image
+                   )
+{
+    const psMetadata *camera = fpa->camera; // Camera configuration for FPA
+    bool mdok = false;                  // Status of MD lookup
+
+    // Get the required information from the camera configuration
+    psMetadata *supps = psMetadataLookupMD(&mdok, camera, "SUPPLEMENTARY"); // Rules for supplementary data
+    if (! mdok || ! supps) {
+        psError(PS_ERR_IO, false, "Unable to find SUPPLEMENTARY in camera configuration!\n");
+        return false;
+    }
+    psString sourceType = psMetadataLookupStr(&mdok, supps, "MASK.SOURCE"); // Type of source: EXT | FILE
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find MASK.SOURCE in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+    psString name = psMetadataLookupStr(&mdok, supps, "MASK.NAME"); // Name of mask
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find MASK.NAME in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+
+    // Go through the FPA to each cell/readout to get the mask
+    p_pmHDU *hdu = fpa->hdu;            // The HDU into which we will read the mask
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int chipNum = 0; chipNum < chips->n; chipNum++) {
+        pmChip *chip = chips->data[chipNum]; // The current chip of interest
+        if (chip->exists && chip->process) {
+            if (chip->hdu) {
+                hdu = chip->hdu;
+            }
+            psArray *cells = chip->cells;       // Array of cells
+            for (int cellNum = 0; cellNum < cells->n; cellNum++) {
+                pmCell *cell = cells->data[cellNum]; // The current cell of interest
+                if (cell->exists && cell->process) {
+                    if (cell->hdu) {
+                        hdu = cell->hdu;
+                    }
+
+                    // Now, need to find out where to write the pixels
+                    psFits *maskDest = psMemIncrRefCounter(fits); // Destination of mask image
+                    psMetadata *header = psMetadataAlloc(); // A dummy header, containing the extension name
+                    if (strcasecmp(sourceType, "FILE") == 0) {
+                        // Source is a file (with optional extension, e.g., "myMaskFile.fits:thisExt"
+                        psString extname = NULL; // Extension name
+                        psString filename = p_pmFPATranslateFileExt(&extname, name, cell); // The filename
+                        psFree(maskDest);
+                        maskDest = psFitsOpen(filename, "rw");
+                        if (extname) {
+                            psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
+                        }
+                        psFree(filename);
+                        psFree(extname);
+                    } else if (strncasecmp(sourceType, "EXT", 3) == 0) {
+                        // Source is an extension in the original file
+                        psString extname = p_pmFPATranslateName(name, cell);
+                        psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
+                        psFree(extname);
+                    }
+
+                    // We've arrived where the pixels are.  Now we need to write them out.
+                    psArray *readouts = cell->readouts; // The array of readouts
+                    for (int readNum = 0; readNum < readouts->n; readNum++) {
+                        pmReadout *readout = readouts->data[readNum]; // The readout of interest
+                        if (! readout->mask) {
+                            psLogMsg(__func__, PS_LOG_WARN, "No mask to write out in %d,%d,%d\n",
+                                     chipNum, cellNum, readNum);
+                        } else {
+                            // XXX: Need to add the extname to the existing header
+                            if (! psFitsWriteImage(maskDest, header, readout->mask, readNum)) {
+                                psError(PS_ERR_IO, false, "Unable to write mask plane %d in extension %s\n",
+                                        readNum, hdu->extname);
+                                return false;
+                            }
+                        }
+                    } // Iterating over readouts
+                    psFree(header);
+                    psFree(maskDest);
+                } // Valid cells
+            } // Iterating over cells
+        } // Valid chips
+    } // Iterating over chips
+
+    return true;
+}
+
+
+bool pmFPAWriteWeight(pmFPA *fpa,       // FPA containing mask to write
+                      psFits *fits      // FITS file for image
+                     )
+{
+    const psMetadata *camera = fpa->camera; // Camera configuration for FPA
+    bool mdok = false;                  // Status of MD lookup
+
+    // Get the required information from the camera configuration
+    psMetadata *supps = psMetadataLookupMD(&mdok, camera, "SUPPLEMENTARY"); // Rules for supplementary data
+    if (! mdok || ! supps) {
+        psError(PS_ERR_IO, false, "Unable to find SUPPLEMENTARY in camera configuration!\n");
+        return false;
+    }
+    psString sourceType = psMetadataLookupStr(&mdok, supps, "WEIGHT.SOURCE"); // Type of source: EXT | FILE
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find WEIGHT.SOURCE in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+    psString name = psMetadataLookupStr(&mdok, supps, "WEIGHT.NAME"); // Name of weight
+    if (! mdok || strlen(sourceType) <= 0) {
+        psError(PS_ERR_IO, false, "Unable to find WEIGHT.NAME in SUPPLEMENTARY section of camera "
+                "configuration!\n");
+        return false;
+    }
+
+    // Go through the FPA to each cell/readout to get the weight
+    p_pmHDU *hdu = fpa->hdu;            // The HDU into which we will read the weight
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int chipNum = 0; chipNum < chips->n; chipNum++) {
+        pmChip *chip = chips->data[chipNum]; // The current chip of interest
+        if (chip->exists && chip->process) {
+            if (chip->hdu) {
+                hdu = chip->hdu;
+            }
+            psArray *cells = chip->cells;       // Array of cells
+            for (int cellNum = 0; cellNum < cells->n; cellNum++) {
+                pmCell *cell = cells->data[cellNum]; // The current cell of interest
+                if (cell->exists && cell->process) {
+                    if (cell->hdu) {
+                        hdu = cell->hdu;
+                    }
+
+                    // Now, need to find out where to write the pixels
+                    psFits *weightDest = psMemIncrRefCounter(fits); // Destination of weight image
+                    psMetadata *header = psMetadataAlloc(); // A dummy header, containing the extension name
+                    if (strcasecmp(sourceType, "FILE") == 0) {
+                        // Source is a file (with optional extension, e.g., "myWeightFile.fits:thisExt"
+                        psString extname = NULL; // Extension name
+                        psString filename = p_pmFPATranslateFileExt(&extname, name, cell); // The filename
+
+                        psFree(weightDest);
+                        weightDest = psFitsOpen(filename, "rw");
+                        if (extname) {
+                            psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
+                        }
+                        psFree(filename);
+                        psFree(extname);
+                    } else if (strncasecmp(sourceType, "EXT", 3) == 0) {
+                        // Source is an extension in the original file
+                        psString extname = p_pmFPATranslateName(name, cell);
+                        psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
+                        psFree(extname);
+                    }
+
+                    // We've arrived where the pixels are.  Now we need to write them out.
+                    psArray *readouts = cell->readouts; // The array of readouts
+                    for (int readNum = 0; readNum < readouts->n; readNum++) {
+                        pmReadout *readout = readouts->data[readNum]; // The readout of interest
+                        if (! readout->weight) {
+                            psLogMsg(__func__, PS_LOG_WARN, "No weight image to write out in %d,%d,%d\n",
+                                     chipNum, cellNum, readNum);
+                        } else {
+                            if (! psFitsWriteImage(weightDest, header, readout->weight, readNum)) {
+                                psError(PS_ERR_IO, false, "Unable to write weight plane %d in extension %s\n",
+                                        readNum, hdu->extname);
+                                return false;
+                            }
+                        }
+                    } // Iterating over readouts
+                    psFree(header);
+                    psFree(weightDest);
+                } // Valid cells
+            } // Iterating over cells
+        } // Valid chips
+    } // Iterating over chips
+
+    return true;
+}
Index: /branches/rel10_ifa/psModules/src/astrom/pmFPAWrite.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmFPAWrite.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmFPAWrite.h	(revision 6448)
@@ -0,0 +1,21 @@
+#ifndef PM_FPA_WRITE_H
+#define PM_FPA_WRITE_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+bool pmFPAWrite(psFits *fits,           // FITS file to which to write
+                pmFPA *fpa,             // FPA to write
+                psDB *db                // Database to update
+               );
+
+bool pmFPAWriteMask(pmFPA *fpa,         // FPA containing mask to write
+                    psFits *fits        // FITS file for image
+                   );
+
+bool pmFPAWriteWeight(pmFPA *fpa,       // FPA containing mask to write
+                      psFits *fits      // FITS file for image
+                     );
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmReadout.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmReadout.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmReadout.c	(revision 6448)
@@ -0,0 +1,26 @@
+#include <stdio.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+
+// Get the bias images for a readout, using the CELL.BIASSEC
+psList *pmReadoutGetBias(pmReadout *readout // Readout for which to get the bias sections
+                        )
+{
+    pmCell *cell = readout->parent;     // The parent cell
+    psList *sections = (psList*)psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC"); // CELL.BIASSEC
+
+    psImage *image = readout->image;    // The image that contains both the biassec and the trimsec
+
+    psList *images = psListAlloc(NULL); // List of images from bias sections
+    psListIterator *sectionsIter = psListIteratorAlloc(sections, PS_LIST_HEAD, true); // Iterator
+    psRegion *region = NULL;            // Bias region from list
+    while ((region = psListGetAndIncrement(sectionsIter))) {
+        psImage *bias = psMemIncrRefCounter(psImageSubset(image, *region)); // Image from bias section
+        psListAdd(images, PS_LIST_TAIL, bias);
+        psFree(bias);
+    }
+    psFree(sectionsIter);
+
+    return images;
+}
Index: /branches/rel10_ifa/psModules/src/astrom/pmReadout.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmReadout.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/pmReadout.h	(revision 6448)
@@ -0,0 +1,12 @@
+#ifndef PM_READOUT_H
+#define PM_READOUT_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Get the bias sections for a specific readout
+psList *pmReadoutGetBias(pmReadout *readout // Readout for which to get the bias sections
+                        );
+
+
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/psAdditionals.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/psAdditionals.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/psAdditionals.c	(revision 6448)
@@ -0,0 +1,169 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "psAdditionals.h"
+
+
+psMetadata *pap_psMetadataCopy(psMetadata *out,
+                               const psMetadata *in)
+{
+    PS_ASSERT_PTR_NON_NULL(in,NULL);
+    if (out ==  NULL) {
+        out = psMetadataAlloc();
+    }
+    psMetadataItem *inItem = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc(*(psMetadata**)&in, PS_LIST_HEAD, NULL);
+    unsigned long numPointers = 0;      // Number of pointers we were forced to copy
+    while ((inItem = psMetadataGetAndIncrement(iter))) {
+        // Need to look for MULTI, which won't be picked up using the iterator.
+        psMetadataItem *multiCheckItem = psMetadataLookup(in, inItem->name);
+        unsigned int flag = PS_META_REPLACE; // Flag to indicate MULTI; otherwise, replace
+        if (multiCheckItem->type == PS_DATA_METADATA_MULTI) {
+            psTrace(__func__, 10, "MULTI: %s (%s)\n", inItem->name, inItem->comment);
+            flag = PS_DATA_METADATA_MULTI;
+        }
+
+        psTrace(__func__, 5, "Copying %s (%s)...\n", inItem->name, inItem->comment);
+
+        #define PS_METADATA_COPY_CASE(NAME,TYPE) \
+    case PS_TYPE_##NAME: \
+        if (! psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_TYPE_##NAME | flag, inItem->comment, \
+                            inItem->data.TYPE)) { \
+            psErrorStackPrint(stderr, "Error copying %s (%s) in the metadata\n", inItem->name, \
+                              inItem->comment); \
+        } \
+        break;
+
+        switch (inItem->type) {
+            // Numerical types
+            PS_METADATA_COPY_CASE(BOOL,B);
+            PS_METADATA_COPY_CASE(S8,S8);
+            PS_METADATA_COPY_CASE(S16,S16);
+            PS_METADATA_COPY_CASE(S32,S32);
+            PS_METADATA_COPY_CASE(U8,U8);
+            PS_METADATA_COPY_CASE(U16,U16);
+            PS_METADATA_COPY_CASE(U32,U32);
+            PS_METADATA_COPY_CASE(F32,F32);
+            PS_METADATA_COPY_CASE(F64,F64);
+
+            // String: relying on the fact that this will copy the string, not point at it.
+        case PS_DATA_STRING:
+            psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_DATA_STRING | flag, inItem->comment,
+                          inItem->data.V);
+            break;
+
+            // Metadata: copy the next level and stuff that in too
+        case PS_DATA_METADATA: {
+                psMetadata *metadata = pap_psMetadataCopy(NULL, inItem->data.md);
+                psMetadataAdd(out, PS_LIST_TAIL, inItem->name, PS_DATA_METADATA | flag, inItem->comment,
+                              metadata);
+                break;
+            }
+            // Other kinds of pointers
+        default:
+            numPointers++;
+            psTrace(__func__, 10, "Copying a pointer in the metadata: %x\n", inItem->type);
+            psMetadataAdd(out, PS_LIST_TAIL, inItem->name, inItem->type | flag, inItem->comment,
+                          inItem->data.V);
+            break;
+        }
+    }
+    psFree(iter);
+
+    if (numPointers > 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "Forced to copy %d pointers when copying metadata.  Updating the "
+                 "copied psMetadata will affect the original!\n", numPointers);
+    }
+
+    return out;
+}
+
+
+void psMetadataPrint(psMetadata *md, int level)
+{
+    psMetadataIterator *iter = psMetadataIteratorAlloc(md, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item = NULL;        // Item from metadata
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        // Indent...
+        for (int i = 0; i < level; i++) {
+            printf(" ");
+        }
+        printf("%s", item->name);
+        if (item->comment && strlen(item->comment) > 0) {
+            printf(" (%s)", item->comment);
+        }
+        printf(": ");
+        switch (item->type) {
+        case PS_DATA_STRING:
+            printf("%s", (char*)item->data.V);
+            break;
+        case PS_DATA_BOOL:
+            if (item->data.B) {
+                printf("True");
+            } else {
+                printf("False");
+            }
+            break;
+        case PS_DATA_S32:
+            printf("%d", item->data.S32);
+            break;
+        case PS_DATA_F32:
+            printf("%f", item->data.F32);
+            break;
+        case PS_DATA_F64:
+            printf("%f", item->data.F64);
+            break;
+        case PS_DATA_METADATA:
+            printf("\n");
+            psMetadataPrint(item->data.V, level + 1);
+            break;
+        default:
+            printf("\n");
+            psError(PS_ERR_IO, false, "Non-printable metadata type: %x\n", item->type);
+        }
+        printf("\n");
+    }
+    psFree(iter);
+
+    return;
+}
+
+// XXX: This should probably be implemented using strpbrk
+psList *psStringSplit(const char *string,
+                      const char *splitters)
+{
+    psList *values = psListAlloc(NULL); // The list of values to return
+    unsigned int length = strlen(string); // The length of the string
+    unsigned int numSplitters = strlen(splitters); // Number of characters that might split
+    unsigned int start = 0;             // The position of the start of a word
+    for (int i = 1; i < length; i++) {
+        bool split = false;             // Is this character a splitter?
+        for (int j = 0; j < numSplitters && ! split; j++) {
+            if (string[i] == splitters[j]) {
+                split = true;
+            }
+        }
+        if (split) {
+            if (i == start) {
+                // Some idiot put in two spaces, or two commas or something
+                start++;
+            } else {
+                // We're at the end of the word
+                psString word = psStringNCopy(&string[start], i - start);
+                (void)psListAdd(values, PS_LIST_TAIL, word);
+                start = i + 1;
+                psFree(word);
+            }
+        }
+    }
+    if (start < length) {
+        // Copy the last word
+        psString word = psStringNCopy(&string[start], length - start);
+        (void)psListAdd(values, PS_LIST_TAIL, word);
+        psFree(word);
+    }
+
+    return values;
+}
+
Index: /branches/rel10_ifa/psModules/src/astrom/psAdditionals.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/psAdditionals.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/astrom/psAdditionals.h	(revision 6448)
@@ -0,0 +1,25 @@
+// Functions that should go into psLib.
+
+
+#ifndef PS_ADDITIONALS_H
+#define PS_ADDITIONALS_H
+
+#include "pslib.h"
+
+// Deep copy of metadata
+// Corrected version of MHPCC code in psLib at the moment
+psMetadata *pap_psMetadataCopy(psMetadata *out, // Target, to which the copy is made
+                               const psMetadata *in // Source, from which the copy is made
+                              );
+
+// Print out the metadata
+void psMetadataPrint(psMetadata *md,    // Metadata to print
+                     int level          // Indent level
+                    );
+
+// Split string on given characters
+psList *psStringSplit(const char *string, // String to split
+                      const char *splitters // Characters on which to split
+                     );
+
+#endif
Index: /branches/rel10_ifa/psModules/src/camera/Makefile.am
===================================================================
--- /branches/rel10_ifa/psModules/src/camera/Makefile.am	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/camera/Makefile.am	(revision 6448)
@@ -3,7 +3,20 @@
 libpsmodulecamera_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
 libpsmodulecamera_la_LDFLAGS  = -release $(PACKAGE_VERSION)
-libpsmodulecamera_la_SOURCES  =
+libpsmodulecamera_la_SOURCES  = \
+	pmChipMosaic.c \
+	pmFPAConceptsGet.c \
+	pmFPAConceptsSet.c \
+	pmFPAConstruct.c \
+	pmFPAMorph.c \
+	pmFPARead.c \
+	pmFPAWrite.c
 
 psmoduleincludedir = $(includedir)
-psmoduleinclude_HEADERS =
-
+psmoduleinclude_HEADERS = \
+	pmChipMosaic.h \
+	pmFPAConceptsGet.h \
+	pmFPAConceptsSet.h \
+	pmFPAConstruct.h \
+	pmFPAMorph.h \
+	pmFPARead.h \
+	pmFPAWrite.h
Index: /branches/rel10_ifa/psModules/src/config/pmConfig.c
===================================================================
--- /branches/rel10_ifa/psModules/src/config/pmConfig.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/config/pmConfig.c	(revision 6448)
@@ -3,6 +3,6 @@
  *  @author PAP, IfA
  *
- *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2006-02-06 21:03:23 $
+ *  @version $Revision: 1.7.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -65,9 +65,9 @@
 {
     PS_ASSERT_PTR_NON_NULL(site, false);
-    PS_ASSERT_PTR_NON_NULL(*site, false);
+    // PS_ASSERT_PTR_NON_NULL(*site, false);
     PS_ASSERT_PTR_NON_NULL(camera, false);
-    PS_ASSERT_PTR_NON_NULL(*camera, false);
+    // PS_ASSERT_PTR_NON_NULL(*camera, false);
     PS_ASSERT_PTR_NON_NULL(recipe, false);
-    PS_ASSERT_PTR_NON_NULL(*recipe, false);
+    // PS_ASSERT_PTR_NON_NULL(*recipe, false);
     PS_ASSERT_INT_POSITIVE(*argc, false);
     PS_ASSERT_PTR_NON_NULL(argv, false);
@@ -264,4 +264,7 @@
 }
 
+
+// XXX EAM : was trying headerItem when it was NULL
+// XXX EAM : should just free & return on first failure
 bool pmConfigValidateCamera(
     const psMetadata *camera,
@@ -284,5 +287,6 @@
         psMetadataItem *headerItem = psMetadataLookup((psMetadata*)header, ruleItem->name);
         if (! headerItem || headerItem->type != ruleItem->type) {
-            match = false;
+            psFree(ruleIter);
+            return false;
         }
 
@@ -294,5 +298,6 @@
             if (strncmp(ruleItem->data.V, headerItem->data.V,
                         strlen(ruleItem->data.V)) != 0) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -302,5 +307,6 @@
                     ruleItem->data.S32, headerItem->data.S32);
             if (ruleItem->data.S32 != headerItem->data.S32) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -309,5 +315,6 @@
                     ruleItem->data.F32, headerItem->data.F32);
             if (ruleItem->data.F32 != headerItem->data.F32) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -316,5 +323,6 @@
                     ruleItem->data.F64, headerItem->data.F64);
             if (ruleItem->data.F64 != headerItem->data.F64) {
-                match = false;
+                psFree(ruleIter);
+                return false;
             }
             break;
@@ -326,5 +334,4 @@
 
     psFree(ruleIter);
-
     return match;
 }
@@ -420,15 +427,9 @@
 pmConfigDB(*site)
  
+XXX: this should allow the option of having NO database server, if chosen by config
 XXX: What should we use for the Database namespace in the call to psDBInit()?
 This is currently NULL.
  *****************************************************************************/
 
-#ifdef OMIT_PSDB
-psDB *pmConfigDB(psMetadata *site)
-{
-    psError(PS_ERR_UNKNOWN, true, "pslib was built without psDB support");
-    return NULL;
-}
-#else
 psDB *pmConfigDB(
     psMetadata *site)
@@ -439,27 +440,14 @@
     psBool mdStatus03 = false;
 
+    // XXX leaky strings
     psString dbServer = psMetadataLookupStr(&mdStatus01, site, "DBSERVER");
     psString dbUsername = psMetadataLookupStr(&mdStatus02, site, "DBUSER");
     psString dbPassword = psMetadataLookupStr(&mdStatus03, site, "DBPASSWORD");
     psString dbName = psMetadataLookupStr(&mdStatus01, site, "DBNAME");
-
     if (!(mdStatus01 & mdStatus02 & mdStatus03)) {
         psLogMsg(__func__, PS_LOG_WARN, "Could not determine database server name, userID, and password from site metadata.\n");
-        return NULL;
-    }
-
-
-
-    psDB *dbh = psDBInit(dbServer, dbUsername, dbPassword, dbName);
-    psFree(dbServer);
-    psFree(dbUsername);
-    psFree(dbPassword);
-    psFree(dbName);
-
-    if (!dbh) {
-        psError(PS_ERR_UNKNOWN, false, "database connection failed");
-    }
-
-    return dbh;
-}
-#endif
+        return(NULL);
+    }
+
+    return(psDBInit(dbServer, dbUsername, dbPassword, dbName));
+}
Index: /branches/rel10_ifa/psModules/src/detrend/pmFlatField.c
===================================================================
--- /branches/rel10_ifa/psModules/src/detrend/pmFlatField.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/detrend/pmFlatField.c	(revision 6448)
@@ -1,2 +1,8 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
 /** @file  pmFlatField.c
  *
@@ -18,6 +24,6 @@
  *  @author Ross Harman, MHPCC
  *
- *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-11-18 19:43:14 $
+ *  @version $Revision: 1.4.12.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -30,5 +36,5 @@
 #include<stdio.h>
 #include<math.h>
-#include <string.h>
+#include <strings.h>
 
 #include "pslib.h"
@@ -36,32 +42,8 @@
 #include "pmMaskBadPixels.h"
 #include "pmFlatFieldErrors.h"
-#include "pmSubtractBias.h"
 
-// XXX: This should be removed when the autoconf stuff handles psConstants.h correctly.
-#define PS_WARN_PTR_NON_NULL(NAME) \
-if ((NAME) == NULL) { \
-    psLogMsg(__func__, PS_LOG_WARN, "WARNING: %s is NULL.", #NAME); \
-} \
 
-bool pmFlatField(
-    pmReadout *in,
-    const pmReadout *flat)
+bool pmFlatField(pmReadout *in, const pmReadout *flat)
 {
-    // XXX: Use the proper image and readout asserts.
-    PS_ASSERT_PTR_NON_NULL(in, true);
-    PS_ASSERT_PTR_NON_NULL(in->image, false);
-    PS_ASSERT_PTR_NON_NULL(in->mask, false);
-    PS_ASSERT_PTR_NON_NULL(flat, false);
-    PS_ASSERT_PTR_NON_NULL(flat->image, false);
-    if (in == NULL)
-        printf("XXX: NULL\n");
-
-    // XXX: Not sure if this is correct.  Must consult with IfA.
-    PS_ASSERT_PTR_NON_NULL(in->mask, false);
-
-    PS_WARN_PTR_NON_NULL(in->parent);
-    if (in->parent != NULL) {
-        PS_WARN_PTR_NON_NULL(in->parent->concepts);
-    }
     int i = 0;
     int j = 0;
@@ -71,28 +53,43 @@
     psElemType flatType;
     psElemType maskType;
-    psImage *inMask = NULL;
-    psImage *flatImage = NULL;
 
-    //
-    // Determine trimmed image from metadata.
-    //
-    psImage *trimmedImg = p_psDetermineTrimmedImage(in);
-    flatImage = flat->image;
-    inMask = in->mask;
+    // Check for nulls
+    if (in == NULL) {
+        return true;       // Readout may not have data in it
+    } else if(flat==NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmFlatField_NULL_FLAT_READOUT);
+        return false;
+    }
+
+    psImage *inImage   = in->image;     // Input image
+    psImage *inMask    = in->mask;      // Mask for input image
+    psImage *flatImage = flat->image;   // Flat-field image
+
+    // Offsets on the chip
+    int x0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.X0");
+    int y0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.Y0");
+    int x0flat = psMetadataLookupS32(NULL, flat->parent->concepts, "CELL.X0");
+    int y0flat = psMetadataLookupS32(NULL, flat->parent->concepts, "CELL.Y0");
+
+    if (inImage == NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmFlatField_NULL_INPUT_IMAGE);
+        return false;
+    } else if(flatImage == NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmFlatField_NULL_FLAT_IMAGE);
+        return false;
+    }
 
     // Check input image and its mask are not larger than flat image
 
-    if (trimmedImg == NULL)
-        printf("XXX: 00\n");
-    if (flatImage == NULL)
-        printf("XXX 01\n");
-
-    if (trimmedImg->numRows>flatImage->numRows || trimmedImg->numCols>flatImage->numCols) {
+    if (inImage->numRows>flatImage->numRows || inImage->numCols>flatImage->numCols) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmFlatField_SIZE_INPUT_IMAGE,
-                 trimmedImg->numRows, trimmedImg->numCols, flatImage->numRows, flatImage->numCols);
+                 inImage->numRows, inImage->numCols, flatImage->numRows, flatImage->numCols);
         return false;
     }
-    if (inMask->numRows > flatImage->numRows || inMask->numCols > flatImage->numCols) {
+    if (inMask && (inMask->numRows > flatImage->numRows || inMask->numCols > flatImage->numCols)) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmFlatField_SIZE_MASK_IMAGE,
@@ -102,6 +99,6 @@
 
     // Determine total offset based on image offset with chip offset
-    totOffCol = trimmedImg->col0 + in->col0;
-    totOffRow = trimmedImg->row0 + in->row0;
+    totOffCol = inImage->col0 + y0in - flatImage->col0 - y0flat;
+    totOffRow = inImage->row0 + x0in - flatImage->row0 - x0flat;
 
     // Check that offsets are within image limits
@@ -111,10 +108,10 @@
                  totOffRow, totOffCol, flatImage->numRows, flatImage->numCols);
         return false;
-    } else if(totOffRow>=trimmedImg->numRows || totOffCol>=trimmedImg->numCols) {
+    } else if(totOffRow>=inImage->numRows || totOffCol>=inImage->numCols) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmFlatField_OFFSET_INPUT_IMAGE,
-                 totOffRow, totOffCol, trimmedImg->numRows, trimmedImg->numCols);
+                 totOffRow, totOffCol, inImage->numRows, inImage->numCols);
         return false;
-    } else if(totOffRow>=inMask->numRows || totOffCol>=inMask->numCols) {
+    } else if(inMask && (totOffRow>=inMask->numRows || totOffCol>=inMask->numCols)) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmFlatField_OFFSET_MASK_IMAGE,
@@ -124,5 +121,5 @@
 
     // Check for incorrect types
-    inType = trimmedImg->type.type;
+    inType = inImage->type.type;
     flatType = flatImage->type.type;
     maskType = inMask->type.type;
@@ -137,5 +134,5 @@
                  flatType);
         return false;
-    } else if(maskType != PS_TYPE_MASK) {
+    } else if(inMask && inMask->type.type != PS_TYPE_MASK) {
         psError( PS_ERR_BAD_PARAMETER_TYPE, true,
                  PS_ERRORTEXT_pmFlatField_TYPE_MASK_IMAGE,
@@ -153,18 +150,20 @@
 case PS_TYPE_##TYPE:                                                                                         \
     /* Per Eugene's request, use two sets of loops: first to fill mask, second to avoid div with bad pix */  \
-    for(j = totOffRow; j < trimmedImg->numRows; j++) {                                                          \
-        for(i = totOffCol; i < trimmedImg->numCols; i++) {                                                      \
+    for(j = totOffRow; j < inImage->numRows; j++) {                                                          \
+        for(i = totOffCol; i < inImage->numCols; i++) {                                                      \
             if(flatImage->data.TYPE[j][i] <= 0.0) {                                                          \
                 /* Negative or zero flat pixels shall be masked in input image as  PM_MASK_FLAT */           \
-                inMask->data.PS_TYPE_MASK_DATA[j][i] |= PM_MASK_FLAT;                                        \
+                if (inMask) {                                                                                \
+                    inMask->data.PS_TYPE_MASK_DATA[j][i] |= PM_MASK_FLAT;                                    \
+                }                                                                                            \
                 flatImage->data.TYPE[j][i] = 0.0;                                                            \
             }                                                                                                \
         }                                                                                                    \
     }                                                                                                        \
-    for(j = totOffRow; j < trimmedImg->numRows; j++) {                                                          \
-        for(i = totOffCol; i < trimmedImg->numCols; i++) {                                                      \
-            if(!inMask->data.PS_TYPE_MASK_DATA[j][i]) {                                                      \
+    for(j = totOffRow; j < inImage->numRows; j++) {                                                          \
+        for(i = totOffCol; i < inImage->numCols; i++) {                                                      \
+            if(inMask && !inMask->data.PS_TYPE_MASK_DATA[j][i]) {                                            \
                 /* Module shall divide the input image by the flat-fielded image */                          \
-                trimmedImg->data.TYPE[j][i] /= flatImage->data.TYPE[j][i];                                      \
+                inImage->data.TYPE[j][i] /= flatImage->data.TYPE[j][i];                                      \
             }                                                                                                \
         }                                                                                                    \
Index: /branches/rel10_ifa/psModules/src/detrend/pmFlatField.h
===================================================================
--- /branches/rel10_ifa/psModules/src/detrend/pmFlatField.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/detrend/pmFlatField.h	(revision 6448)
@@ -1,2 +1,8 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
 /** @file  pmFlatField.h
  *
@@ -18,6 +24,6 @@
  *  @author Ross Harman, MHPCC
  *
- *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-10-20 23:06:24 $
+ *  @version $Revision: 1.2.12.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -25,5 +31,5 @@
 
 #include "pslib.h"
-#include "pmAstrometry.h"
+#include "pmFPA.h"
 
 
Index: /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixels.c
===================================================================
--- /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixels.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixels.c	(revision 6448)
@@ -1,2 +1,7 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /** @file  pmMaskBadPixels.c
  *
@@ -19,6 +24,6 @@
  *  @author Ross Harman, MHPCC
  *
- *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-11-18 19:43:14 $
+ *  @version $Revision: 1.3.12.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -31,157 +36,77 @@
 #include<stdio.h>
 #include<math.h>
-#include<string.h>
+#include<strings.h>
 
 #include "pmMaskBadPixels.h"
 #include "pmMaskBadPixelsErrors.h"
-#include "pmSubtractBias.h"
 
-//XXX: REmove, autoconf is broken.
-#define PS_WARN_PTR_NON_NULL(NAME) \
-if ((NAME) == NULL) { \
-    psLogMsg(__func__, PS_LOG_WARN, "WARNING: %s is NULL.", #NAME); \
-} \
+bool pmMaskBadPixels(pmReadout *in, const psImage *mask, unsigned int maskVal, float sat,
+                     unsigned int growVal, int grow)
+{
+    int i = 0;
+    int j = 0;
+    int jj = 0;
+    int ii = 0;
+    int rRound = 0;
+    int rowMin = 0;
+    int rowMax = 0;
+    int colMin = 0;
+    int colMax = 0;
+    int totOffCol = 0;
+    int totOffRow = 0;
+    float r = 0.0f;
+    psElemType inType;
+    psElemType maskType;
+    psImage *inImage = NULL;
+    psImage *inMask = NULL;
 
 
-/******************************************************************************
-GrowPixel(inMask, row, col, radius, maskVal): This private routine takes an
-input image mask and a pixel location, then sets (logical or) all pixels with
-parameter radius if that pixel to maskVal parameter.
-*****************************************************************************/
-psBool GrowPixel(
-    psImage *inMask,
-    psS32 col,
-    psS32 row,
-    psS32 radius,
-    psU32 maskVal)
-{
-    psS32 rowMin = PS_MAX(row-radius, 0);
-    psS32 rowMax = PS_MIN(row+radius+1, inMask->numRows);
-    psS32 colMin = PS_MAX(col-radius, 0);
-    psS32 colMax = PS_MIN(col+radius+1, inMask->numCols);
-    psF32 squareRadius = PS_SQR(radius);
+    // Check for nulls
+    if (in == NULL) {
+        return true;   // Readout may not have data in it
+    } else if(mask==NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_NULL_MASK_IMAGE);
+        return false;
+    }
 
-
-    for(psS32 i=rowMin; i<rowMax; i++) {
-        for(psS32 j=colMin; j<colMax; j++) {
-            // Old code:
-            //            psF32 rRound = 0.5 + sqrtf((psF32) (((col-j)*(col-j)) + ((row-i)*(row-i))));
-            //            if(rRound <= radius) {
-            psF32 squareDis = (psF32) (((col-j)*(col-j)) + ((row-i)*(row-i)));
-            if (squareDis <= squareRadius) {
-                inMask->data.U8[i][j] |= maskVal;
-            }
-        }
-    }
-    return(true);
-}
-
-/******************************************************************************
-GetRadius(inImg, col, row, sat, growVal, grow): This private routine takes an
-input image and pixel location and determines what radius that pixel must grow
-by.
- 
-//XXX: Inline this or macro it for speed.
- *****************************************************************************/
-static psS32 GetRadius(
-    psImage *inImg,
-    psS32 col,
-    psS32 row,
-    psF32 sat,
-    psU32 growVal,
-    psS32 grow)
-{
-    psS32 growRadius = 0;
-    if (inImg->type.type == PS_TYPE_F32) {
-        if(inImg->data.F32[row][col] == growVal) {
-            growRadius = grow;
-        }
-        if (inImg->data.F32[row][col] > sat) {
-            growRadius = PS_MAX(growRadius+1, 2);
-        }
-    } else if (inImg->type.type == PS_TYPE_S32) {
-        if(inImg->data.S32[row][col] == growVal) {
-            growRadius = grow;
-        }
-        if (inImg->data.S32[row][col] > sat) {
-            growRadius = PS_MAX(growRadius+1, 2);
-        }
-    } else if (inImg->type.type == PS_TYPE_U16) {
-        if(inImg->data.U16[row][col] == growVal) {
-            growRadius = grow;
-        }
-        if (inImg->data.U16[row][col] > sat) {
-            growRadius = PS_MAX(growRadius+1, 2);
-        }
-    } else {
-        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
-                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_MASK_IMAGE,
-                 inImg->type.type);
-    }
-    return(growRadius);
-}
-
-
-bool pmMaskBadPixels(
-    pmReadout *in,
-    const pmReadout *mask,
-    unsigned int maskVal,
-    float sat,
-    unsigned int growVal,
-    int grow)
-{
-    // XXX: Review this code then put all asserts at the top.
-    PS_ASSERT_PTR_NON_NULL(in, true);
-    PS_ASSERT_PTR_NON_NULL(in->image, false);
-    PS_WARN_PTR_NON_NULL(in->parent);
-    if (in->parent != NULL) {
-        PS_WARN_PTR_NON_NULL(in->parent->concepts);
-    }
-    PS_ASSERT_PTR_NON_NULL(mask, false);
-    int i = 0;
-    int j = 0;
-    int totOffCol = 0;
-    int totOffRow = 0;
-    psElemType inType;
-    psElemType maskType;
-    psImage *inMask = NULL;
-
-    //
-    // Determine trimmed image from metadata.
-    //
-    psImage *trimmedImg = p_psDetermineTrimmedImage(in);
-    if(in->mask == NULL) {
-        in->mask = psImageAlloc(trimmedImg->numCols, trimmedImg->numRows, PS_TYPE_MASK);
-        PS_IMAGE_SET_U8(in->mask, 0);
+    inImage = in->image;
+    if (inImage == NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_NULL_INPUT_IMAGE);
+        return false;
+    } else if(in->mask == NULL) {
+        in->mask = psImageAlloc(inImage->numCols, inImage->numRows, PS_TYPE_MASK);
+        memset(in->mask->data.V[0], 0, inImage->numCols*inImage->numRows*PSELEMTYPE_SIZEOF(PS_TYPE_MASK));
     }
     inMask = in->mask;
 
     // Check input image and its mask are not larger than mask
-    if(trimmedImg->numRows > mask->image->numRows || trimmedImg->numCols > mask->image->numCols) {
+    if(inImage->numRows > mask->numRows || inImage->numCols > mask->numCols) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE,
-                 trimmedImg->numRows, trimmedImg->numCols, mask->image->numRows, mask->image->numCols);
+                 inImage->numRows, inImage->numCols, mask->numRows, mask->numCols);
         return false;
-    } else if(inMask->numRows > mask->image->numRows || inMask->numCols > mask->image->numCols) {
+    } else if(inMask->numRows>mask->numRows || inMask->numCols>mask->numCols) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmMaskBadPixels_SIZE_MASK_IMAGE,
-                 inMask->numRows, inMask->numCols, mask->image->numRows, mask->image->numCols);
+                 inMask->numRows, inMask->numCols, mask->numRows, mask->numCols);
         return false;
     }
 
     // Determine total offset based on image offset with chip offset
-    totOffCol = trimmedImg->col0 + in->col0;
-    totOffRow = trimmedImg->row0 + in->row0;
+    totOffCol = inImage->col0 + in->col0;
+    totOffRow = inImage->row0 + in->row0;
 
     // Check that offsets are within image limits
-    if(totOffRow>=mask->image->numRows || totOffCol>=mask->image->numCols) {
+    if(totOffRow>=mask->numRows || totOffCol>=mask->numCols) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmMaskBadPixels_OFFSET_MASK_IMAGE,
-                 totOffRow, totOffCol, mask->image->numRows, mask->image->numCols);
+                 totOffRow, totOffCol, mask->numRows, mask->numCols);
         return false;
-    } else if(totOffRow>=trimmedImg->numRows || totOffCol>=trimmedImg->numCols) {
+    } else if(totOffRow>=inImage->numRows || totOffCol>=inImage->numCols) {
         psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                  PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE,
-                 totOffRow, totOffCol, trimmedImg->numRows, trimmedImg->numCols);
+                 totOffRow, totOffCol, inImage->numRows, inImage->numCols);
         return false;
     } else if(totOffRow>=inMask->numRows || totOffCol>=inMask->numCols) {
@@ -193,6 +118,6 @@
 
     // Check for incorrect types
-    inType = trimmedImg->type.type;
-    maskType = mask->image->type.type;
+    inType = inImage->type.type;
+    maskType = mask->type.type;
     if(PS_IS_PSELEMTYPE_COMPLEX(inType)) {
         psError( PS_ERR_BAD_PARAMETER_TYPE, true,
@@ -207,34 +132,57 @@
     }
 
-    //
-    // This is the main loop which examines each pixel and masks pixels if
-    //    A: The mask matches the maskValue
-    //    B: The pixel is larger than the saturation value
-    //    C: The pixel equals the grow value (in which case a circle is masked)
-    //
-    for(i=totOffRow; i<trimmedImg->numRows; i++) {
-        for(j=totOffCol; j<trimmedImg->numCols; j++) {
-            //
-            // A: Pixels which satisfy maskVal shall be masked
-            //
-            if (mask->image->data.U8[i][j] == maskVal) {
-                in->mask->data.U8[i][j] |= maskVal;
-            }
+    // Macro for all PS types
+    #define PM_BAD_PIXELS(TYPE)                                                                              \
+case PS_TYPE_##TYPE:                                                                                         \
+    for(j=totOffRow; j<inImage->numRows; j++) {                                                              \
+        for(i=totOffCol; i<inImage->numCols; i++) {                                                          \
+            \
+            /* Pixels with flux greater than sat shall be masked */                                          \
+            if(inImage->data.TYPE[j][i] > sat) {                                                             \
+                inMask->data.PS_TYPE_MASK_DATA[j][i] |= PM_MASK_SAT;                                         \
+            }                                                                                                \
+            \
+            /* Pixels which satisfy maskVal shall be masked */                                               \
+            inMask->data.PS_TYPE_MASK_DATA[j][i] |= (mask->data.PS_TYPE_MASK_DATA[j][i]&maskVal);            \
+            \
+            /* Pixels which satisfy growVal and within the grow radius shall be masked */                    \
+            if(mask->data.PS_TYPE_MASK_DATA[j][i] & growVal) {                                               \
+                rowMin = MAX(j-grow, 0);                                                                     \
+                rowMax = MIN(j+grow+1, inImage->numRows);                                                    \
+                colMin = MAX(i-grow, 0);                                                                     \
+                colMax = MIN(i+grow+1, inImage->numCols);                                                    \
+                for(jj=rowMin; jj<rowMax; jj++) {                                                            \
+                    for(ii=colMin; ii<colMax; ii++) {                                                        \
+                        r = sqrtf((ii-i)*(ii-i)+(jj-j)*(jj-j));                                              \
+                        rRound = r + 0.5;                                                                    \
+                        if(rRound <= grow) {                                                                 \
+                            inMask->data.PS_TYPE_MASK_DATA[jj][ii] |=                                        \
+                                    (mask->data.PS_TYPE_MASK_DATA[j][i]&growVal);                            \
+                        }                                                                                    \
+                    }                                                                                        \
+                }                                                                                            \
+            }                                                                                                \
+        }                                                                                                    \
+    }                                                                                                        \
+    break;
 
-            //
-            // We first determine how much we need to grow by and store this in
-            // growRadius.
-            //
-            psS32 growRadius = GetRadius(trimmedImg, j, i, sat, growVal, grow);
-
-            //
-            // Grow the pixel mask if necessary.
-            //
-            if (growRadius != 0) {
-                GrowPixel(in->mask, j, i, growRadius, maskVal);
-            }
-        }
+    // Switch to call bad pixel masking macro defined above
+    switch(inType) {
+        PM_BAD_PIXELS(U8);
+        PM_BAD_PIXELS(U16);
+        PM_BAD_PIXELS(U32);
+        PM_BAD_PIXELS(U64);
+        PM_BAD_PIXELS(S8);
+        PM_BAD_PIXELS(S16);
+        PM_BAD_PIXELS(S32);
+        PM_BAD_PIXELS(S64);
+        PM_BAD_PIXELS(F32);
+        PM_BAD_PIXELS(F64);
+    default:
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_UNSUPPORTED,
+                 inType);
     }
 
-    return true;
+    return false;
 }
Index: /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixels.h
===================================================================
--- /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixels.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixels.h	(revision 6448)
@@ -1,2 +1,7 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /** @file  pmMaskBadPixels.h
  *
@@ -19,6 +24,6 @@
  *  @author Ross Harman, MHPCC
  *
- *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-11-15 20:09:03 $
+ *  @version $Revision: 1.2.12.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -26,5 +31,5 @@
 
 #include "pslib.h"
-#include "pmAstrometry.h"
+#include "pmFPA.h"
 
 /** Mask values */
@@ -33,8 +38,8 @@
     PM_MASK_BADCOL  = 0x0002,   ///< The pixel is a bad column.
     PM_MASK_SAT     = 0x0004,   ///< The pixel is saturated.
-    PM_MASK_FLAT    = 0x0008    ///< The pixel is non-positive in the flat-field.
+    PM_MASK_BAD     = 0x0008,   ///< The pixel is low
+    PM_MASK_FLAT    = 0x0010    ///< The pixel is non-positive in the flat-field.
 } pmMaskValue;
 
-// XXX: Use PS_MIN, PS_MAX
 /** Macro to find maximum of two numbers */
 #define MAX(A,B)((A)>=(B)?(A):(B))
@@ -54,5 +59,5 @@
 bool pmMaskBadPixels(
     pmReadout *in,          ///< Readout containing input image data.
-    const pmReadout *mask,   ///< Mask data to be added to readout mask data.
+    const psImage *mask,    ///< Mask data to be added to readout mask data.
     unsigned int maskVal,   ///< Mask value to determine what to add to input mask.
     float sat,              ///< Saturation limit to mask bad pixels.
Index: /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixelsErrors.h
===================================================================
--- /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixelsErrors.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/detrend/pmMaskBadPixelsErrors.h	(revision 6448)
@@ -1,2 +1,7 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /** @file  pmMaskBadPixelsErrors.h
  *
@@ -7,6 +12,6 @@
  *  @author Ross Harman, MHPCC
  *
- *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-09-28 20:43:52 $
+ *  @version $Revision: 1.1.20.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -23,5 +28,5 @@
  *     $2  The error text (rest of the line in pmMaskBadPixelsErrors.h)
  *     $n  The order of the source line in pmMaskBadPixelsErrors.h (comments excluded)
- * 
+ *
  * DO NOT EDIT THE LINES BETWEEN //~Start and //~End!  ANY CHANGES WILL BE OVERWRITTEN.
  */
Index: /branches/rel10_ifa/psModules/src/detrend/pmNonLinear.c
===================================================================
--- /branches/rel10_ifa/psModules/src/detrend/pmNonLinear.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/detrend/pmNonLinear.c	(revision 6448)
@@ -1,2 +1,7 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /** @file  pmNonLinear.c
  *
@@ -5,6 +10,6 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-12-05 20:49:40 $
+ *  @version $Revision: 1.5.12.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -22,22 +27,14 @@
 
 #include "pmNonLinear.h"
-#include "pmSubtractBias.h"
 
-// XXX: Remove, autoconf must be
-#define PS_WARN_PTR_NON_NULL(NAME) \
-if ((NAME) == NULL) { \
-    psLogMsg(__func__, PS_LOG_WARN, "WARNING: %s is NULL.", #NAME); \
-} \
 /******************************************************************************
 pmNonLinearityLookup(): This routine will take an pmReadout image as input
 and a 1-D polynomial.  For each pixel in the input image, the polynomial will
-be evaluated at that pixels value, and the image pixel will then be set
-to
+be evaluated at that pixels value, and the image pixel will then be set to
 that value.
-*****************************************************************************/
+ *****************************************************************************/
 
-pmReadout *pmNonLinearityPolynomial(
-    pmReadout *inputReadout,
-    const psPolynomial1D *input1DPoly)
+pmReadout *pmNonLinearityPolynomial(pmReadout *inputReadout,
+                                    const psPolynomial1D *input1DPoly)
 {
     PS_ASSERT_PTR_NON_NULL(inputReadout, NULL);
@@ -45,18 +42,11 @@
     PS_ASSERT_IMAGE_TYPE(inputReadout->image, PS_TYPE_F32, NULL);
     PS_ASSERT_PTR_NON_NULL(input1DPoly, NULL);
-    PS_WARN_PTR_NON_NULL(inputReadout->parent);
-    if (inputReadout->parent != NULL) {
-        PS_WARN_PTR_NON_NULL(inputReadout->parent->concepts);
-    }
 
-    //
-    // Determine trimmed image from metadata.
-    //
-    psImage *trimmedImg = p_psDetermineTrimmedImage(inputReadout);
+    psS32 i;
+    psS32 j;
 
-    for (psS32 i=0;i<trimmedImg->numRows;i++) {
-        for (psS32 j=0;j<trimmedImg->numCols;j++) {
-            trimmedImg->data.F32[i][j] = psPolynomial1DEval(input1DPoly,
-                                         trimmedImg->data.F32[i][j]);
+    for (i=0;i<inputReadout->image->numRows;i++) {
+        for (j=0;j<inputReadout->image->numCols;j++) {
+            inputReadout->image->data.F32[i][j] = psPolynomial1DEval(input1DPoly, inputReadout->image->data.F32[i][j]);
         }
     }
@@ -71,48 +61,68 @@
 inFluxe, and the corresponding value in outFlux.  The image pixel will then
 be set to the value from outFlux.
- 
-XXX: Must assert that filename exists.  This should probably happen in
-the lookup files.
  *****************************************************************************/
-pmReadout *pmNonLinearityLookup(
-    pmReadout *inputReadout,
-    const char *filename
-)
+pmReadout *pmNonLinearityLookup(pmReadout *inputReadout,
+                                const psVector *inFlux,
+                                const psVector *outFlux)
 {
     PS_ASSERT_PTR_NON_NULL(inputReadout,NULL);
     PS_ASSERT_PTR_NON_NULL(inputReadout->image,NULL);
     PS_ASSERT_IMAGE_TYPE(inputReadout->image, PS_TYPE_F32, NULL);
-    PS_WARN_PTR_NON_NULL(inputReadout->parent);
-    if (inputReadout->parent != NULL) {
-        PS_WARN_PTR_NON_NULL(inputReadout->parent->concepts);
+    PS_ASSERT_PTR_NON_NULL(inFlux,NULL);
+    if (inFlux->n < 2) {
+        psError(PS_ERR_UNKNOWN,true, "pmNonLinearityLookup(): input vector less than 2 elements.  Returning inputReadout image.");
+        return(inputReadout);
     }
-    //
-    // Determine trimmed image from metadata.
-    //
-    psImage *trimmedImg = p_psDetermineTrimmedImage(inputReadout);
+    PS_ASSERT_PTR_NON_NULL(outFlux,NULL);
+    psS32 tableSize = inFlux->n;
+    if (inFlux->n != outFlux->n) {
+        tableSize = PS_MIN(inFlux->n, outFlux->n);
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmNonLinear.c: pmNonLinearityLookup(): input vectors have different sizes (%d, %d)\n", inFlux->n, outFlux->n);
+    }
+    PS_ASSERT_VECTOR_TYPE(inFlux, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(outFlux, PS_TYPE_F32, NULL);
 
-    psLookupTable *tmpLT = psLookupTableAlloc(filename, "%f %f", 0);
-    psS32 numLines = psLookupTableRead(tmpLT);
+    psS32 i;
+    psS32 j;
+    psS32 binNum;
+    psScalar x;
     psS32 numPixels = 0;
-    if (numLines < 2) {
-        psLogMsg(__func__, PS_LOG_WARN,
-                 "WARNING: Lookup Table is too small.  Returning original pmReadout.\n");
-    } else {
-        for (psS32 i=0;i<trimmedImg->numRows;i++) {
-            for (psS32 j=0;j<trimmedImg->numCols;j++) {
-                psF64 tmpD = psLookupTableInterpolate(tmpLT, trimmedImg->data.F32[i][j], 1);
-                if (!isnan(tmpD)) {
-                    trimmedImg->data.F32[i][j] = tmpD;
-                } else {
-                    numPixels++;
-                }
+    psF32 slope;
+
+    x.type.type = PS_TYPE_F32;
+    for (i=0;i<inputReadout->image->numRows;i++) {
+        for (j=0;j<inputReadout->image->numCols;j++) {
+            x.data.F32 = inputReadout->image->data.F32[i][j];
+            binNum = p_psVectorBinDisect((psVector *)inFlux, &x);
+
+            if (binNum == -2) {
+                // We get here if x is below the table lookup range.
+                inputReadout->image->data.F32[i][j] = outFlux->data.F32[0];
+                numPixels++;
+
+            } else if (binNum == -1) {
+                // We get here if x is above the table lookup range.
+                inputReadout->image->data.F32[i][j] = outFlux->data.F32[tableSize-1];
+                numPixels++;
+
+            } else if (binNum < -2) {
+                // We get here if there was some other problem.
+                psError(PS_ERR_UNKNOWN,true, "pmNonLinearityLookup(): Could not perform p_psVectorBinDisect().  Returning inputReadout image.");
+                return(inputReadout);
+                numPixels++;
+            } else {
+                // Perform linear interpolation.
+                slope = (outFlux->data.F32[binNum+1] - outFlux->data.F32[binNum]) /
+                        (inFlux->data.F32[binNum+1]  - inFlux->data.F32[binNum]);
+                inputReadout->image->data.F32[i][j] = outFlux->data.F32[binNum] +
+                                                      ((x.data.F32 - inFlux->data.F32[binNum]) * slope);
             }
         }
-        if (numPixels > 0) {
-            psLogMsg(__func__, PS_LOG_WARN,
-                     "WARNING: pmNonLinear.c: pmNonLinearityLookup(): %d pixels outside table.", numPixels);
-        }
     }
-
+    if (numPixels > 0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmNonLinear.c: pmNonLinearityLookup(): %d pixels outside table.", numPixels);
+    }
     return(inputReadout);
 }
Index: /branches/rel10_ifa/psModules/src/detrend/pmNonLinear.h
===================================================================
--- /branches/rel10_ifa/psModules/src/detrend/pmNonLinear.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/detrend/pmNonLinear.h	(revision 6448)
@@ -1,2 +1,7 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /** @file  pmNonLinear.h
  *
@@ -5,6 +10,6 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-10-20 23:06:24 $
+ *  @version $Revision: 1.2.12.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -16,15 +21,12 @@
 
 #include "pslib.h"
-#include "pmAstrometry.h"
+#include "pmFPA.h"
 
-pmReadout *pmNonLinearityPolynomial(
-    pmReadout *in,
-    const psPolynomial1D *coeff
-);
+pmReadout *pmNonLinearityPolynomial(pmReadout *in,
+                                    const psPolynomial1D *coeff);
 
-pmReadout *pmNonLinearityLookup(
-    pmReadout *in,
-    const char *filename
-);
+pmReadout *pmNonLinearityLookup(pmReadout *in,
+                                const psVector *inFlux,
+                                const psVector *outFlux);
 
 #endif
Index: /branches/rel10_ifa/psModules/src/imcombine/pmReadoutCombine.c
===================================================================
--- /branches/rel10_ifa/psModules/src/imcombine/pmReadoutCombine.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/imcombine/pmReadoutCombine.c	(revision 6448)
@@ -5,6 +5,6 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2006-02-06 21:03:25 $
+ *  @version $Revision: 1.5.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -36,15 +36,287 @@
 }
 
+static void pmCombineParamsFree (pmCombineParams *params)
+{
+
+    if (params == NULL)
+        return;
+
+    psFree (params->stats);
+    return;
+}
+
+pmCombineParams *pmCombineParamsAlloc (psStatsOptions statsOptions)
+{
+
+    pmCombineParams *params = psAlloc (sizeof(pmCombineParams));
+    psMemSetDeallocator(params, (psFreeFunc) pmCombineParamsFree);
+
+    params->stats = psStatsAlloc (statsOptions);
+    params->maskVal = 0;
+    params->fracHigh = 0.25;
+    params->fracHigh = 0.25;
+    params->nKeep = 3;
+
+    return (params);
+}
+
 /******************************************************************************
 XXX: Must add support for S16 and S32 types.  F32 currently supported.
  *****************************************************************************/
 psImage *pmReadoutCombine(psImage *output,
-                          const psList *inputs,
-                          psCombineParams *params,
+                          const psArray *inputs,
                           const psVector *zero,
                           const psVector *scale,
+                          pmCombineParams *params,
                           bool applyZeroScale,
                           psF32 gain,
                           psF32 readnoise)
+{
+    PS_ASSERT_PTR_NON_NULL(inputs, NULL);
+    PS_ASSERT_PTR_NON_NULL(params, NULL);
+    PS_ASSERT_PTR_NON_NULL(params->stats, NULL);
+    if (zero != NULL) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, NULL);
+        //        PS_ASSERT_VECTOR_TYPE_S16_S32_F32(zero, NULL);
+    }
+    if (scale != NULL) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, NULL);
+        //        PS_ASSERT_VECTOR_TYPE_S16_S32_F32(scale, NULL);
+    }
+    if ((zero != NULL) && (scale != NULL)) {
+        PS_ASSERT_VECTOR_TYPE_EQUAL(zero, scale, NULL);
+        // PS_ASSERT_VECTOR_TYPE_S16_S32_F32(scale, NULL);
+    }
+
+    psStats *stats = params->stats;
+    psS32 maxInputCols = 0;
+    psS32 maxInputRows = 0;
+    psS32 minInputCols = PS_MAX_S32;
+    psS32 minInputRows = PS_MAX_S32;
+    pmReadout *tmpReadout = NULL;
+    psS32 tmpI;
+    psElemType outputType = PS_TYPE_F32;
+
+    if (DetermineNumBits(stats->options) != 1) {
+        psError(PS_ERR_UNKNOWN, true,
+                "Multiple statistical options have been requested.  Returning NULL.\n");
+        return(NULL);
+    }
+
+    // We step through each readout in the input image list.  If any readout is
+    // NULL, empty, or has the wrong type, we generate an error and return
+    // NULL.  We determine how big of an output image is needed to combine
+    // these input images.  We do this by taking the
+    //     max(readout->col0 + readout->numCols + image->col0 + image->numCols)
+    //     max(readout->row0 + readout->numRows + image->row0 + image->numRows)
+    //
+    for (int i = 0; i < inputs->n; i++) {
+        tmpReadout = inputs->data[i];
+        PS_ASSERT_READOUT_NON_NULL(tmpReadout, output);
+        PS_ASSERT_READOUT_NON_EMPTY(tmpReadout, output);
+        PS_ASSERT_READOUT_TYPE(tmpReadout, PS_TYPE_F32, output);
+
+        minInputRows = PS_MIN(minInputRows, (tmpReadout->row0 + tmpReadout->image->row0));
+        tmpI = tmpReadout->row0 +
+               tmpReadout->image->row0 +
+               tmpReadout->image->numRows;
+        maxInputRows = PS_MAX(maxInputRows, tmpI);
+
+        minInputCols = PS_MIN(minInputCols, (tmpReadout->col0 + tmpReadout->image->col0));
+        tmpI = tmpReadout->col0 +
+               tmpReadout->image->col0 +
+               tmpReadout->image->numCols;
+        maxInputCols = PS_MAX(maxInputCols, tmpI);
+    }
+
+    // We ensure that the zero vector is of the proper size.
+    if (zero != NULL) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, NULL);
+        if (zero->n < inputs->n) {
+            psError(PS_ERR_UNKNOWN, true, "zero vector has incorrect size (%d).  Returning NULL.\n", zero->n);
+            return(NULL);
+        } else if (zero->n > inputs->n) {
+            // XXX EAM : abort on this condition? is probably an error
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: the zero vector too many elements (%d)\n", zero->n);
+        }
+    }
+
+    // We ensure that the scale vector is of the proper size.
+    if (scale != NULL) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, NULL);
+        if (scale->n < inputs->n) {
+            psError(PS_ERR_UNKNOWN, true, "scale vector has incorrect size (%d).  Returning NULL.\n", scale->n);
+            return(NULL);
+        } else if (scale->n > inputs->n) {
+            // XXX EAM : abort on this condition? is probably an error
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: the scale vector has too many elements (%d)\n", scale->n);
+        }
+    }
+
+    // At this point, the following variables have been computed:
+    // maxInputRows: the largest input row value, in output image space.
+    // maxInputCols: the largest input column value, in output image space.
+    // minInputRows: the smallest input row value, in output image space.
+    // minInputCols: the smallest input column value, in output image space.
+    //
+    if (output == NULL) {
+        output = psImageAlloc(maxInputCols-minInputCols, maxInputRows-minInputRows, outputType);
+        *(psS32 *) &(output->col0) = minInputCols;
+        *(psS32 *) &(output->row0) = minInputRows;
+    } else {
+
+        // XXX EAM : recycle the existing output image?  why would we care about the existing pixels?
+        PS_ASSERT_IMAGE_TYPE(output, PS_TYPE_F32, NULL);
+        if (((output->col0 + output->numCols) < maxInputCols) ||
+                ((output->row0 + output->numRows) < maxInputRows)) {
+            psError(PS_ERR_UNKNOWN, true,
+                    "Output image (%d, %d) is too small to hold combined images.  Returning NULL.\n",
+                    output->row0 + output->numRows,
+                    output->col0 + output->numCols);
+            return(NULL);
+        }
+
+        // reset output origin using logic of above
+        *(psS32 *) &(output->col0) = minInputCols;
+        *(psS32 *) &(output->row0) = minInputRows;
+    }
+
+    psVector *tmpPixels     = psVectorAlloc(inputs->n, PS_TYPE_F32);
+    psVector *tmpPixelsKeep = psVectorAlloc(inputs->n, PS_TYPE_F32);
+    psVector *outRowLower   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+    psVector *outRowUpper   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+    psVector *outColLower   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+    psVector *outColUpper   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+
+    // For each input readout, we store the min/max pixel indices for that readout, in detector coordinates,
+    // in the psVectors (outRowLower, outColLower, outRowUpper, outColUpper).
+    for (int i = 0; i < inputs->n; i++) {
+        tmpReadout = (pmReadout *) inputs->data[i];
+        outRowLower->data.U32[i] = tmpReadout->row0 + tmpReadout->image->row0;
+        outColLower->data.U32[i] = tmpReadout->col0 + tmpReadout->image->col0;
+        outRowUpper->data.U32[i] = tmpReadout->row0 +
+                                   tmpReadout->image->row0 +
+                                   tmpReadout->image->numRows;
+        outColUpper->data.U32[i] = tmpReadout->col0 +
+                                   tmpReadout->image->col0 +
+                                   tmpReadout->image->numCols;
+    }
+
+    // We loop through each pixel in the output image.  We loop through each
+    // input readout.  We determine if that output pixel is contained in the
+    // image from that readout.  If so, we save it in psVector tmpPixels.
+    // If not, we set a mask for that element in tmpPixels.  Then, we mask off
+    // pixels not between fracLow and fracHigh.  Then we call the vector
+    // stats routine on those pixels/mask.  Then we set the output pixel value
+    // to the result of the stats call.
+
+    int nx, ny;
+    int nKeep, nMin;
+    float keepFrac = 1.0 - params->fracLow - params->fracHigh;
+    float value = 0;
+    psF32 *saveVector = tmpPixelsKeep->data.F32;
+
+    for (int j = output->row0; j < (output->row0 + output->numRows) ; j++) {
+        if (j % 10 == 0)
+            fprintf (stderr, ".");
+        for (int i = output->col0; i < (output->col0 + output->numCols) ; i++) {
+            int nPix = 0;
+            for (int r = 0; r < inputs->n; r++) {
+                tmpReadout = (pmReadout *) inputs->data[r];
+
+                // psTrace (__func__, 6, "[%d][%d]: [%d][%d] to [%d][%d]\n", i, j, outColLower->data.U32[r], outRowLower->data.U32[r], outColUpper->data.U32[r], outRowUpper->data.U32[r]);
+                if (i <  outColLower->data.U32[r])
+                    continue;
+                if (i >= outColUpper->data.U32[r])
+                    continue;
+                if (j <  outRowLower->data.U32[r])
+                    continue;
+                if (j >= outRowUpper->data.U32[r])
+                    continue;
+
+                nx = i - (tmpReadout->col0 + tmpReadout->image->col0);
+                ny = j - (tmpReadout->row0 + tmpReadout->image->row0);
+
+                if (tmpReadout->mask != NULL) {
+                    if (tmpReadout->mask->data.U8[ny][nx] && params->maskVal)
+                        continue;
+                }
+
+                tmpPixels->data.F32[nPix] = tmpReadout->image->data.F32[ny][nx];
+                // psTrace (__func__, 6, "readout[%d], image [%d][%d] is %f\n", r, i, j, tmpPixels->data.F32[nPix]);
+                nPix ++;
+            }
+            tmpPixels->n = nPix;
+
+            // are there enough valid pixels to apply fracLow,fracHigh?
+            nKeep = nPix * keepFrac;
+            if (nKeep >= params->nKeep) {
+                psVectorSort (tmpPixels, tmpPixels);
+                nMin = nPix * params->fracLow;
+                tmpPixelsKeep->data.F32 = &tmpPixels->data.F32[nMin];
+                tmpPixelsKeep->n = nKeep;
+            } else {
+                tmpPixelsKeep->data.F32 = tmpPixels->data.F32;
+                tmpPixelsKeep->n = nPix;
+            }
+
+            // tmpPixelsKeep is already sorted.  sample mean and median are very easy
+            if (stats->options & PS_STAT_SAMPLE_MEAN) {
+                value = 0;
+                for (int r = 0; r < tmpPixelsKeep->n; r++) {
+                    value += tmpPixelsKeep->data.F32[r];
+                }
+                if (tmpPixelsKeep->n == 0) {
+                    value = 0;
+                } else {
+                    value = value / tmpPixelsKeep->n;
+                }
+            }
+            if (stats->options & PS_STAT_SAMPLE_MEDIAN) {
+                int r = tmpPixelsKeep->n / 2;
+                if (tmpPixelsKeep->n == 0) {
+                    value = 0;
+                    goto got_value;
+                }
+                if (tmpPixelsKeep->n % 2 == 1) {
+                    int r = 0.5*tmpPixelsKeep->n;
+                    value = tmpPixelsKeep->data.F32[r];
+                    goto got_value;
+                }
+                if (tmpPixelsKeep->n % 2 == 0) {
+                    value = 0.5*(tmpPixelsKeep->data.F32[r] +
+                                 tmpPixelsKeep->data.F32[r-1]);
+                    goto got_value;
+                }
+            }
+got_value:
+            output->data.F32[j-output->row0][i-output->col0] = value;
+        }
+    }
+    tmpPixelsKeep->data.F32 = saveVector;
+
+    psFree(tmpPixels);
+    psFree(tmpPixelsKeep);
+    psFree(outRowLower);
+    psFree(outRowUpper);
+    psFree(outColLower);
+    psFree(outColUpper);
+
+    return(output);
+}
+
+/******************************************************************************
+XXX: Must add support for S16 and S32 types.  F32 currently supported.
+ *****************************************************************************/
+psImage *pmReadoutCombine_OLD(psImage *output,
+                              const psList *inputs,
+                              pmCombineParams *params,
+                              const psVector *zero,
+                              const psVector *scale,
+                              bool applyZeroScale,
+                              psF32 gain,
+                              psF32 readnoise)
 {
     PS_ASSERT_PTR_NON_NULL(inputs, NULL);
@@ -410,6 +682,6 @@
     psRegion minRegion;
     psRegion maxRegion;
-    psStats *minStats = psStatsAlloc(PS_STAT_FITTED_MEAN);
-    psStats *maxStats = psStatsAlloc(PS_STAT_FITTED_MEAN);
+    psStats *minStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    psStats *maxStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
     psStats *diffStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
     psVector *diffs = psVectorAlloc(fringePoints->n, PS_TYPE_F32);
@@ -445,6 +717,6 @@
         }
 
-        fp->midValue = 0.5 * (maxStats->fittedMean + minStats->fittedMean);
-        fp->delta = maxStats->fittedMean - minStats->fittedMean;
+        fp->midValue = 0.5 * (maxStats->robustMedian + minStats->robustMedian);
+        fp->delta = maxStats->robustMedian - minStats->robustMedian;
         diffs->data.F32[i] = fp->delta;
     }
@@ -455,5 +727,5 @@
     psFree(diffs);
     if (diffStats == NULL) {
-        psError(PS_ERR_UNKNOWN, true, "Could not determine fitted median of the differences.\n");
+        psError(PS_ERR_UNKNOWN, true, "Could not determine robust median of the differences.\n");
         return(NULL);
     }
@@ -461,115 +733,2 @@
 }
 
-
-
-/**
- *
- * The input array fluxLevels consists of Ni vectors, one per mosaic image.
- * Each vector consists of Nj elements, each a measurement of the input
- * flat-field image flux levels. All of these vectors must be constructed with
- * the same number of elements, or the function will return an error. If a chip
- * is missing from a particular image, that element should be set to NaN. The
- * vector chipGains supplies initial guesses for the chip gains. If the vector
- * contains the values 0.0 or NaN for any of the elements, the gain is set to the
- * mean of the valid values. If the vector length does not match the number of
- * chips, an warning is raised, all chip gain guesses will be set to 1.0, and the
- * vector length modified to match the number of chips defined by the supplied
- * fluxLevels. The sourceFlux input vector must be allocated (not NULL), but the
- * routine will set the vector length to the number of source images regardless
- * of the initial state of the vector. All vectors used by this function must be
- * of type PS_DATA_F64.
- *
- 
-fluxLevels(i, j): for each flat field image i, this psArray contains a vector
-with an elemenmt for each chip j.  So, fluxLevels(i, j) corresponds to the
-measured flux M_(i, j) for flat image i, chip j.
- 
-chipGains[]: has j elements, one for each chip.
- 
- 
-They have the observed flux levels for each chip of each image.  They want to
-solve for the actual flux levels and the gain of each chip.
- 
-Okay, they want to solve for source fluxes and chip gains.
- 
- *
- */
-bool pmFlatNormalization(
-    psVector *sourceFlux,
-    psVector *chipGains,
-    psArray *fluxLevels)
-{
-    PS_ASSERT_PTR_NON_NULL(fluxLevels, false);
-    psS32 numImages = fluxLevels->n;
-    psS32 numChips = ((psVector *) fluxLevels->data[0])->n;
-    for (psS32 i = 0 ; i < numImages ; i++) {
-        psVector *tmpVec = (psVector *) fluxLevels->data[i];
-        PS_ASSERT_VECTOR_NON_NULL(tmpVec, false);
-        PS_ASSERT_VECTOR_TYPE(tmpVec, PS_TYPE_F64, false);
-        PS_ASSERT_VECTOR_SIZE(tmpVec, numChips, false);
-    }
-
-    //
-    // Ensure that *localChipGains points to a vector of the same length as numImages.
-    //
-    PS_ASSERT_PTR_NON_NULL(chipGains, false);
-    PS_ASSERT_VECTOR_TYPE(chipGains, PS_TYPE_F64, false);
-    psVector *localChipGains = chipGains;
-    if (numChips != chipGains->n) {
-        psLogMsg(__func__, PS_LOG_WARN, "WARNING: the chipGains vector length does not match the number of chips.\n");
-        localChipGains = psVectorAlloc(numChips, PS_TYPE_F64);
-        psBool rc = psVectorInit(localChipGains, 1.0);
-        if (rc == false) {
-            printf("XXX: gen error\n");
-        }
-    }
-
-    //
-    // If the chipGains vector contains the values 0.0 or NaN for any of the elements,
-    // the gain is set to the mean of the valid values.
-    //
-    psBool meanFlag = false;
-    psVector *chipGainsMask = psVectorAlloc(chipGains->n, PS_TYPE_U8);
-    for (psS32 i = 0 ; i < chipGains->n ; i++) {
-        if ((fabs(chipGains->data.F64[i]) < FLT_EPSILON) ||
-                (isnan(chipGains->data.F64[i]))) {
-            chipGainsMask->data.U8[i] = 1;
-            meanFlag = true;
-        }
-    }
-    // Must calculate the mean.
-    if (meanFlag == true) {
-        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
-        stats = psVectorStats(stats, chipGains, NULL, chipGainsMask, 1);
-        if (stats == NULL) {
-            printf("XXX: gen error\n");
-        }
-        psF64 mean;
-        psBool rc = p_psGetStatValue(stats, &mean);
-        if (rc == false) {
-            printf("XXX: gen error\n");
-        }
-        // Set the gain to this mean for chips with a gain of 0.0 or NAN
-
-        for (psS32 i = 0 ; i < chipGains->n ; i++) {
-            if ((fabs(chipGains->data.F64[i]) < FLT_EPSILON) ||
-                    (isnan(chipGains->data.F64[i]))) {
-                chipGains->data.F64[i] = mean;
-            }
-        }
-    }
-
-    //
-    // Assert that sourceFlux is non-NULL, correct type, correct size.
-    //
-    PS_ASSERT_PTR_NON_NULL(sourceFlux, false);
-    PS_ASSERT_VECTOR_TYPE(sourceFlux, PS_TYPE_F64, false);
-    psVectorRealloc(sourceFlux, numImages);
-
-    //    psFree(psVector);
-    if (numImages != chipGains->n) {
-        psFree(localChipGains);
-    }
-
-    return(true);
-}
Index: /branches/rel10_ifa/psModules/src/imcombine/pmReadoutCombine.h
===================================================================
--- /branches/rel10_ifa/psModules/src/imcombine/pmReadoutCombine.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/imcombine/pmReadoutCombine.h	(revision 6448)
@@ -5,6 +5,6 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2006-01-26 21:10:50 $
+ *  @version $Revision: 1.3.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:41 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -23,5 +23,5 @@
 #include "pslib.h"
 #include "psConstants.h"
-#include "pmAstrometry.h"
+#include "pmFPA.h"
 
 typedef struct
@@ -33,11 +33,13 @@
     int nKeep;
 }
-psCombineParams;
+pmCombineParams;
+
+pmCombineParams *pmCombineParamsAlloc (psStatsOptions statsOptions);
 
 psImage *pmReadoutCombine(psImage *output,
-                          const psList *inputs,
-                          psCombineParams *params,
+                          const psArray *inputs,
                           const psVector *zero,
                           const psVector *scale,
+                          pmCombineParams *params,
                           bool applyZeroScale,
                           float gain,
@@ -72,29 +74,3 @@
 pmFringePoint;
 
-
-/**
- * 
- * The input array fluxLevels consists of Ni vectors, one per mosaic image.
- * Each vector consists of Nj elements, each a measurement of the input
- * flat-field image flux levels. All of these vectors must be constructed with
- * the same number of elements, or the function will return an error. If a chip
- * is missing from a particular image, that element should be set to NaN. The
- * vector chipGains supplies initial guesses for the chip gains. If the vector
- * contains the values 0.0 or NaN for any of the elements, the gain is set to the
- * mean of the valid values. If the vector length does not match the number of
- * chips, an warning is raised, all chip gain guesses will be set to 1.0, and the
- * vector length modified to match the number of chips defined by the supplied
- * fluxLevels. The sourceFlux input vector must be allocated (not NULL), but the
- * routine will set the vector length to the number of source images regardless
- * of the initial state of the vector. All vectors used by this function must be
- * of type PS_DATA_F64.
- * 
- */
-bool pmFlatNormalization(
-    psVector *sourceFlux,
-    psVector *chipGains,
-    psArray *fluxLevels
-);
-
-
 #endif
Index: /branches/rel10_ifa/psModules/src/imsubtract/Makefile.am
===================================================================
--- /branches/rel10_ifa/psModules/src/imsubtract/Makefile.am	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/imsubtract/Makefile.am	(revision 6448)
@@ -4,10 +4,10 @@
 libpsmoduleimsubtract_la_LDFLAGS  = -release $(PACKAGE_VERSION)
 libpsmoduleimsubtract_la_SOURCES  = pmImageSubtract.c \
-    pmSubtractBias.c \
-    pmSubtractSky.c
+    pmSubtractBias.c
+#    pmSubtractSky.c
 
 psmoduleincludedir = $(includedir)
 psmoduleinclude_HEADERS = \
     pmImageSubtract.h \
-    pmSubtractBias.h \
-    pmSubtractSky.h
+    pmSubtractBias.h
+#    pmSubtractSky.h
Index: /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractBias.c
===================================================================
--- /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractBias.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractBias.c	(revision 6448)
@@ -1,2 +1,7 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /** @file  pmSubtractBias.c
  *
@@ -6,28 +11,28 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2006-02-06 21:03:25 $
+ *  @version $Revision: 1.9.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
  *
  */
-/*****************************************************************************/
-/* INCLUDE FILES                                                             */
-/*****************************************************************************/
-#include <stdio.h>
-#include <math.h>
-#include <string.h>
-#include "pslib.h"
+
 #if HAVE_CONFIG_H
 #include <config.h>
 #endif
+
+#include <assert.h>
 #include "pmSubtractBias.h"
 
-/*****************************************************************************/
-/* DEFINE STATEMENTS                                                         */
-/*****************************************************************************/
+#define PM_SUBTRACT_BIAS_POLYNOMIAL_ORDER 2
+#define PM_SUBTRACT_BIAS_SPLINE_ORDER 3
+
+
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+
 // XXX: put these in psConstants.h
-void PS_POLY1D_PRINT(
-    psPolynomial1D *poly)
+void PS_POLY1D_PRINT(psPolynomial1D *poly)
 {
     printf("-------------- PS_POLY1D_PRINT() --------------\n");
@@ -57,105 +62,98 @@
 }\
 
-/*****************************************************************************/
-/* TYPE DEFINITIONS                                                          */
-/*****************************************************************************/
-
-/*****************************************************************************/
-/* GLOBAL VARIABLES                                                          */
-/*****************************************************************************/
-psS32 currentId = 0;                // XXX: remove
-psS32 memLeaks = 0;                 // XXX: remove
-//PRINT_MEMLEAKS(8); XXX
-/*****************************************************************************/
-/* FILE STATIC VARIABLES                                                     */
-/*****************************************************************************/
-
-/*****************************************************************************/
-/* FUNCTION IMPLEMENTATION - LOCAL                                           */
-/*****************************************************************************/
+
+void overscanOptionsFree(pmOverscanOptions *options)
+{
+    psFree(options->stat);
+    psFree(options->poly);
+    psFree(options->spline);
+}
+
+pmOverscanOptions *pmOverscanOptionsAlloc(bool single, pmFit fitType, unsigned int order, psStats *stat)
+{
+    pmOverscanOptions *opts = psAlloc(sizeof(pmOverscanOptions));
+    psMemSetDeallocator(opts, (psFreeFunc)overscanOptionsFree);
+
+    // Inputs
+    opts->single = single;
+    opts->fitType = fitType;
+    opts->order = order;
+    opts->stat = psMemIncrRefCounter(stat);
+
+    // Outputs
+    opts->poly = NULL;
+    opts->spline = NULL;
+
+    return opts;
+}
+
 
 /******************************************************************************
-psSubtractFrame(): this routine will take as input the pmReadout for the input
-image and a pmReadout for the bias image.  The bias image is subtracted in
-place from the input image.  We assume that sizes and types are checked
-elsewhere.
- 
-XXX: Verify that the image and readout offsets are being used the right way.
- 
-XXX: Ensure that it does the correct thing with image size.
+psSubtractFrame(): this routine will take as input a readout for the input
+image and a readout for the bias image.  The bias image is subtracted in
+place from the input image.
 *****************************************************************************/
-static pmReadout *SubtractFrame(
-    pmReadout *in,
-    const pmReadout *bias)
-{
-    // XXX: When did the ->row0 and ->col0 offsets get coded?
-    for (psS32 i=0;i<in->image->numRows;i++) {
-        for (psS32 j=0;j<in->image->numCols;j++) {
-            in->image->data.F32[i][j]-=
-                bias->image->data.F32[i+in->row0-bias->row0][j+in->col0-bias->col0];
-
-            if ((in->mask != NULL) && (bias->mask != NULL)) {
-                (in->mask->data.U8[i][j])|=
-                    bias->mask->data.U8[i+in->row0-bias->row0][j+in->col0-bias->col0];
+static bool SubtractFrame(pmReadout *in,// Input readout
+                          const pmReadout *sub, // Readout to be subtracted from input
+                          float scale   // Scale to apply before subtracting
+                         )
+{
+    assert(in);
+    assert(sub);
+
+    psImage *inImage  = in->image;      // The input image
+    psImage *inMask   = in->mask;       // The input mask
+    psImage *subImage = sub->image;     // The image to be subtracted
+    psImage *subMask  = sub->mask;      // The mask for the subtraction image
+
+    // Offsets of the cells
+    int x0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.X0");
+    int y0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.Y0");
+    int x0sub = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.X0");
+    int y0sub = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.Y0");
+
+    if ((inImage->numCols + x0in - x0sub) > subImage->numCols) {
+        psError(PS_ERR_UNKNOWN, true, "Image does not have enough columns for subtraction.\n");
+        return false;
+    }
+    if ((inImage->numRows + y0in - y0sub) > subImage->numRows) {
+        psError(PS_ERR_UNKNOWN, true, "Image does not have enough rows for subtraction.\n");
+        return false;
+    }
+
+    if (scale == 1.0) {
+        for (int i = 0; i < inImage->numRows; i++) {
+            for (int j = 0; j < inImage->numCols; j++) {
+                inImage->data.F32[i][j] -= subImage->data.F32[i+y0in-y0sub][j+x0in-x0sub];
+                if (inMask && subMask) {
+                    inMask->data.U8[i][j] |= subMask->data.U8[i+y0in-y0sub][j+x0in-x0sub];
+                }
             }
         }
-    }
-
-    return(in);
-}
-
-
-/******************************************************************************
-psSubtractDarkFrame(): this routine will take as input the pmReadout for the
-input image and a pmReadout for the dark image.  The dark image is scaled and
-subtracted in place from the input image.
- 
-XXX: Verify that the image and readout offsets are being used the right way.
- 
-XXX: Ensure that it does the correct thing with image size.
-*****************************************************************************/
-static pmReadout *SubtractDarkFrame(
-    pmReadout *in,
-    const pmReadout *dark,
-    psF32 scale)
-{
-    // XXX: When did the ->row0 and ->col0 offsets get coded?
-    if (fabs(scale) > FLT_EPSILON) {
-        for (psS32 i=0;i<in->image->numRows;i++) {
-            for (psS32 j=0;j<in->image->numCols;j++) {
-                in->image->data.F32[i][j]-=
-                    (scale * dark->image->data.F32[i+in->row0-dark->row0][j+in->col0-dark->col0]);
-
-                if ((in->mask != NULL) && (dark->mask != NULL)) {
-                    (in->mask->data.U8[i][j])|=
-                        dark->mask->data.U8[i+in->row0-dark->row0][j+in->col0-dark->col0];
+    } else {
+        for (int i = 0; i < inImage->numRows; i++) {
+            for (int j = 0; j < inImage->numCols; j++) {
+                inImage->data.F32[i][j] -= subImage->data.F32[i+y0in-y0sub][j+x0in-x0sub] * scale;
+                if (inMask && subMask) {
+                    inMask->data.U8[i][j] |= subMask->data.U8[i+y0in-y0sub][j+x0in-x0sub];
                 }
             }
         }
-    } else {
-        for (psS32 i=0;i<in->image->numRows;i++) {
-            for (psS32 j=0;j<in->image->numCols;j++) {
-                in->image->data.F32[i][j]-=
-                    dark->image->data.F32[i+in->row0-dark->row0][j+in->col0-dark->col0];
-
-                if ((in->mask != NULL) && (dark->mask != NULL)) {
-                    (in->mask->data.U8[i][j])|=
-                        dark->mask->data.U8[i+in->row0-dark->row0][j+in->col0-dark->col0];
-                }
-            }
-        }
-    }
-
-    return(in);
-}
-
+    }
+
+    return true;
+}
+
+
+#if 0
 /******************************************************************************
 ImageSubtractScalar(): subtract a scalar from the input image.
  
-XXX: Is there a psLib function for this?
+XXX: Use a psLib function for this.
+ 
+XXX: This should
  *****************************************************************************/
-static psImage *ImageSubtractScalar(
-    psImage *image,
-    psF32 scalar)
+static psImage *ImageSubtractScalar(psImage *image,
+                                    psF32 scalar)
 {
     for (psS32 i=0;i<image->numRows;i++) {
@@ -166,4 +164,5 @@
     return(image);
 }
+#endif
 
 /******************************************************************************
@@ -179,12 +178,4 @@
     psStatsOptions opt = 0;
 
-    /*
-        if (stat->options & PS_STAT_ROBUST_MODE) {
-            if (numOptions == 0) {
-                opt = PS_STAT_ROBUST_MODE;
-            }
-            numOptions++;
-        }
-    */
     if (stat->options & PS_STAT_ROBUST_MEDIAN) {
         if (numOptions == 0) {
@@ -194,11 +185,4 @@
     }
 
-    if (stat->options & PS_STAT_FITTED_MEAN) {
-        if (numOptions == 0) {
-            opt = PS_STAT_FITTED_MEAN;
-        }
-        numOptions++;
-    }
-
     if (stat->options & PS_STAT_CLIPPED_MEAN) {
         if (numOptions == 0) {
@@ -222,5 +206,5 @@
 
     if (numOptions == 0) {
-        psError(PS_ERR_UNKNOWN,true, "No allowable statistics options have been specified.\n");
+        psError(PS_ERR_UNKNOWN,true, "No statistics options have been specified.\n");
     }
     if (numOptions != 1) {
@@ -231,96 +215,7 @@
 }
 
-/******************************************************************************
-Polynomial1DCopy(): This private function copies the members of the existing
-psPolynomial1D "in" into the existing psPolynomial1D "out".  The previous
-members of the existing psPolynomial1D "out" are psFree'ed.
- *****************************************************************************/
-static psBool Polynomial1DCopy(
-    psPolynomial1D *out,
-    psPolynomial1D *in)
-{
-    psFree(out->coeff);
-    psFree(out->coeffErr);
-    psFree(out->mask);
-
-    out->type = in->type;
-    out->nX = in->nX;
-
-    out->coeff = (psF64 *) psAlloc((in->nX + 1) * sizeof(psF64));
-    // XXX: use memcpy
-    for (psS32 i = 0 ; i < (in->nX + 1) ; i++) {
-        out->coeff[i] = in->coeff[i];
-    }
-
-    out->coeffErr = (psF64 *) psAlloc((in->nX + 1) * sizeof(psF64));
-    // XXX: use memcpy
-    for (psS32 i = 0 ; i < (in->nX + 1) ; i++) {
-        out->coeffErr[i] = in->coeffErr[i];
-    }
-
-    out->mask = (psMaskType *) psAlloc((in->nX + 1) * sizeof(psMaskType));
-    // XXX: use memcpy
-    for (psS32 i = 0 ; i < (in->nX + 1) ; i++) {
-        out->mask[i] = in->mask[i];
-    }
-
-    return(true);
-}
-
-/******************************************************************************
-Polynomial1DDup(): This private function duplicates and then returns the input
-psPolynomial1D "in".
- *****************************************************************************/
-static psPolynomial1D *Polynomial1DDup(
-    psPolynomial1D *in)
-{
-    psPolynomial1D *out = psPolynomial1DAlloc(in->type, in->nX);
-    Polynomial1DCopy(out, in);
-    return(out);
-}
-
-
-/******************************************************************************
-SplineCopy(): This private function copies the members of the existing
-psSpline in into the existing psSpline out.
- *****************************************************************************/
-static psBool SplineCopy(
-    psSpline1D *out,
-    psSpline1D *in)
-{
-    PS_ASSERT_PTR_NON_NULL(out, false);
-    PS_ASSERT_PTR_NON_NULL(in, false);
-
-    for (psS32 i = 0 ; i < out->n ; i++) {
-        psFree(out->spline[i]);
-    }
-    psFree(out->spline);
-    psFree(out->knots);
-    psFree(out->p_psDeriv2);
-
-    out->n = in->n;
-    out->spline = (psPolynomial1D **) psAlloc(in->n * sizeof(psPolynomial1D *));
-    for (psS32 i = 0 ; i < in->n ; i++) {
-        out->spline[i] = Polynomial1DDup(in->spline[i]);
-    }
-
-    // XXX: use psVectorCopy if they get it working.
-    out->knots = psVectorAlloc(in->knots->n, in->knots->type.type);
-    for (psS32 i = 0 ; i < in->knots->n ; i++) {
-        out->knots->data.F32[i] = in->knots->data.F32[i];
-    }
-    /*
-        out->knots = psVectorCopy(out->knots, in->knots, in->knots->type.type);
-    */
-
-    out->p_psDeriv2 = (psF32 *) psAlloc((in->n + 1) * sizeof(psF32));
-    // XXX: use memcpy
-    for (psS32 i = 0 ; i < (in->n + 1) ; i++) {
-        out->p_psDeriv2[i] = in->p_psDeriv2[i];
-    }
-
-    return(true);
-}
-
+
+
+#if 0
 /******************************************************************************
 ScaleOverscanVector(): this routine takes as input an arbitrary vector,
@@ -329,14 +224,16 @@
     PM_FIT_POLYNOMIAL: fit a polynomial to the entire input vector data.
     PM_FIT_SPLINE: fit splines to the input vector data.
-The resulting spline or polynomial is set in the fitSpec argument.
+XXX: Doesn't it make more sense to do polynomial interpolation on a few
+elements of the input vector, rather than fit a polynomial to the entire
+vector?
  *****************************************************************************/
-static psVector *ScaleOverscanVector(
-    psVector *overscanVector,
-    psS32 n,
-    void *fitSpec,
-    pmFit fit)
+static psVector *ScaleOverscanVector(psVector *overscanVector,
+                                     psS32 n,
+                                     void *fitSpec,
+                                     pmFit fit)
 {
     psTrace(".psModule.pmSubtracBias.ScaleOverscanVector", 4,
             "---- ScaleOverscanVector() begin (%d -> %d) ----\n", overscanVector->n, n);
+    //    PS_VECTOR_PRINT_F32(overscanVector);
 
     if (NULL == overscanVector) {
@@ -351,16 +248,20 @@
     //
     if (n == overscanVector->n) {
-        return(psVectorCopy(newVec, overscanVector, PS_TYPE_F32));
-    }
+        for (psS32 i = 0 ; i < n ; i++) {
+            newVec->data.F32[i] = overscanVector->data.F32[i];
+        }
+        return(newVec);
+    }
+    psPolynomial1D *myPoly;
+    psSpline1D *mySpline;
     psF32 x;
-
+    psS32 i;
     if (fit == PM_FIT_POLYNOMIAL) {
         // Fit a polynomial to the old overscan vector.
-        psPolynomial1D *myPoly = (psPolynomial1D *) fitSpec;
+        myPoly = (psPolynomial1D *) fitSpec;
         PS_ASSERT_POLY_NON_NULL(myPoly, NULL);
-        PS_ASSERT_POLY1D(myPoly, NULL);
         myPoly = psVectorFitPolynomial1D(myPoly, NULL, 0, overscanVector, NULL, NULL);
         if (myPoly == NULL) {
-            psError(PS_ERR_UNKNOWN, false, "Could not fit a polynomial to the psVector.\n");
+            psError(PS_ERR_UNKNOWN, false, "ScaleOverscanVector()(1): Could not fit a polynomial to the psVector.\n");
             return(NULL);
         }
@@ -369,9 +270,17 @@
         // of the old vector, use the fitted polynomial to determine the
         // interpolated value at that point, and set the new vector.
-        for (psS32 i=0;i<n;i++) {
+        for (i=0;i<n;i++) {
             x = ((psF32) i) * ((psF32) overscanVector->n) / ((psF32) n);
             newVec->data.F32[i] = psPolynomial1DEval(myPoly, x);
         }
     } else if (fit == PM_FIT_SPLINE) {
+        psS32 mustFreeSpline = 0;
+        // Fit a spline to the old overscan vector.
+        mySpline = (psSpline1D *) fitSpec;
+        // XXX: Does it make any sense to have a psSpline argument?
+        if (mySpline == NULL) {
+            mustFreeSpline = 1;
+        }
+
         //
         // NOTE: Since the X arg in the psVectorFitSpline1D() function is NULL,
@@ -379,26 +288,26 @@
         // properly when doing the spline eval.
         //
-        psSpline1D *mySpline = psVectorFitSpline1D(NULL, overscanVector);
+        //        mySpline = psVectorFitSpline1D(mySpline, NULL, overscanVector, NULL);
+        mySpline = psVectorFitSpline1D(NULL, overscanVector);
         if (mySpline == NULL) {
-            psError(PS_ERR_UNKNOWN, false, "Could not fit a spline to the psVector.\n");
+            psError(PS_ERR_UNKNOWN, false, "ScaleOverscanVector()(2): Could not fit a spline to the psVector.\n");
             return(NULL);
         }
+        //        PS_PRINT_SPLINE(mySpline);
 
         // For each element of the new vector, convert the x-ordinate to that
-        // of the old vector, use the fitted spline to determine the
+        // of the old vector, use the fitted polynomial to determine the
         // interpolated value at that point, and set the new vector.
-        for (psS32 i=0;i<n;i++) {
+        for (i=0;i<n;i++) {
             // Scale to [0 : overscanVector->n - 1]
             x = ((psF32) i) * ((psF32) (overscanVector->n-1)) / ((psF32) n);
             newVec->data.F32[i] = psSpline1DEval(mySpline, x);
         }
-
-        psSpline1D *ptrSpline = (psSpline1D *) fitSpec;
-        if (ptrSpline != NULL) {
-            // Copy the resulting spline fit into ptrSpline.
-            PS_ASSERT_SPLINE(ptrSpline, NULL);
-            SplineCopy(ptrSpline, mySpline);
-        }
-        psFree(mySpline);
+        if (mustFreeSpline ==1) {
+            psFree(mySpline);
+        }
+        //        PS_VECTOR_PRINT_F32(newVec);
+
+
     } else {
         psError(PS_ERR_UNKNOWN, true, "unknown fit type.  Returning NULL.\n");
@@ -412,867 +321,257 @@
 }
 
+#endif
+
+// Produce an overscan vector from an array of pixels
+static psVector *overscanVector(pmOverscanOptions *overscanOpts, // Overscan options
+                                const psArray *pixels, // Array of vectors containing the pixel values
+                                psStats *myStats // Statistic to use in reducing the overscan
+                               )
+{
+    // Reduce the overscans
+    psVector *reduced = psVectorAlloc(pixels->n, PS_TYPE_F32); // Overscan for each row
+    psVector *ordinate = psVectorAlloc(pixels->n, PS_TYPE_F32); // Ordinate
+    psVector *mask = psVectorAlloc(pixels->n, PS_TYPE_U8); // Mask for fitting
+    for (int i = 0; i < pixels->n; i++) {
+        psVector *values = pixels->data[i]; // Vector with overscan values
+        if (values->n > 0) {
+            mask->data.U8[i] = 0;
+            ordinate->data.F32[i] = 2.0*(float)i/(float)pixels->n - 1.0; // Scale to [-1,1]
+            psVectorStats(myStats, values, NULL, NULL, 0);
+            double reducedVal = NAN; // Result of statistics
+            if (! p_psGetStatValue(myStats, &reducedVal)) {
+                psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result "
+                        "of statistics on row %d.\n", i);
+                return NULL;
+            }
+            reduced->data.F32[i] = reducedVal;
+        } else if (overscanOpts->fitType == PM_FIT_NONE) {
+            psError(PS_ERR_UNKNOWN, true, "The overscan is not supplied for all points on the "
+                    "image, and no fit is requested.\n");
+            return NULL;
+        } else {
+            // We'll fit this one out
+            mask->data.U8[i] = 1;
+        }
+    }
+
+    // Fit the overscan, if required
+    switch (overscanOpts->fitType) {
+    case PM_FIT_NONE:
+        // No fitting --- that's easy.
+        break;
+    case PM_FIT_POLY_ORD:
+        if (overscanOpts->poly && (overscanOpts->poly->nX != overscanOpts->order ||
+                                   overscanOpts->poly->type != PS_POLYNOMIAL_ORD)) {
+            psFree(overscanOpts->poly);
+            overscanOpts->poly = NULL;
+        }
+        if (! overscanOpts->poly) {
+            overscanOpts->poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, overscanOpts->order);
+        }
+        psVectorFitPolynomial1D(overscanOpts->poly, mask, 1, reduced, NULL, ordinate);
+        psFree(reduced);
+        reduced = psPolynomial1DEvalVector(overscanOpts->poly, ordinate);
+        break;
+    case PM_FIT_POLY_CHEBY:
+        if (overscanOpts->poly && (overscanOpts->poly->nX != overscanOpts->order ||
+                                   overscanOpts->poly->type != PS_POLYNOMIAL_CHEB)) {
+            psFree(overscanOpts->poly);
+            overscanOpts->poly = NULL;
+        }
+        if (! overscanOpts->poly) {
+            overscanOpts->poly = psPolynomial1DAlloc(PS_POLYNOMIAL_CHEB, overscanOpts->order);
+        }
+        psVectorFitPolynomial1D(overscanOpts->poly, mask, 1, reduced, NULL, ordinate);
+        psFree(reduced);
+        reduced = psPolynomial1DEvalVector(overscanOpts->poly, ordinate);
+        break;
+    case PM_FIT_SPLINE:
+        // XXX I don't think psSpline1D is up to scratch yet --- it has no mask, and requires an
+        // input spline
+        overscanOpts->spline = psVectorFitSpline1D(reduced, ordinate);
+        psFree(reduced);
+        reduced = psSpline1DEvalVector(overscanOpts->spline, ordinate);
+        break;
+    default:
+        psError(PS_ERR_UNKNOWN, true, "Unknown value for the fitting type: %d\n", overscanOpts->fitType);
+        return NULL;
+        break;
+    }
+
+    psFree(ordinate);
+    psFree(mask);
+
+    return reduced;
+}
+
+
+
 /******************************************************************************
+XXX: The SDRS does not specify type support.  F32 is implemented here.
  *****************************************************************************/
-static psS32 GetOverscanSize(
-    psImage *inImg,
-    pmOverscanAxis overScanAxis)
-{
-    if (overScanAxis == PM_OVERSCAN_ROWS) {
-        return(inImg->numCols);
-    } else if (overScanAxis == PM_OVERSCAN_COLUMNS) {
-        return(inImg->numRows);
-    } else if (overScanAxis == PM_OVERSCAN_ALL) {
-        return(1);
-    }
-    return(0);
-}
-
-/******************************************************************************
-GetOverscanAxis(in) this private routine determines the appropiate overscan
-axis from the parent cell metadata.
- 
-XXX: Verify the READDIR corresponds with my overscan axis.
- *****************************************************************************/
-static pmOverscanAxis GetOverscanAxis(pmReadout *in)
-{
-    psBool rc;
-    if ((in->parent != NULL) && (in->parent->concepts)) {
-        psS32 dir = psMetadataLookupS32(&rc, in->parent->concepts, "CELL.READDIR");
-        if (rc == true) {
-            if (dir == 1) {
-                return(PM_OVERSCAN_ROWS);
-            } else if (dir == 2) {
-                return(PM_OVERSCAN_COLUMNS);
-            } else if (dir == 3) {
-                return(PM_OVERSCAN_ALL);
-            }
-        }
-    }
-
-    psLogMsg(__func__, PS_LOG_WARN,
-             "WARNING: pmSubtractBias.(): could not determine CELL.READDIR from in->parent metadata.  Setting overscan axis to PM_OVERSCAN_NONE.\n");
-    return(PM_OVERSCAN_NONE);
-}
-
-/******************************************************************************
-psListLength(list): determine the length of a psList.
- 
-XXX: Put this elsewhere.
- *****************************************************************************/
-static psS32 psListLength(
-    psList *list)
-{
-    psS32 length = 0;
-    psListElem *tmpElem = (psListElem *) list->head;
-    while (NULL != tmpElem) {
-        tmpElem = tmpElem->next;
-        length++;
-    }
-    return(length);
-}
-
-/******************************************************************************
-Note: this isn't needed anymore as of psModule SDRS 12-09.
- *****************************************************************************/
-static psBool OverscanReducePixel(
-    psImage *in,
-    psList *bias,
-    psStats *myStats)
-{
-    PS_ASSERT_PTR_NON_NULL(in, NULL);
-    PS_ASSERT_PTR_NON_NULL(bias, NULL);
-    PS_ASSERT_PTR_NON_NULL(bias->head, NULL);
-    PS_ASSERT_PTR_NON_NULL(myStats, NULL);
-
-    // Allocate a psVector with one element per overscan image.
-    psS32 numOverscanImages = psListLength(bias);
-    psVector *statsAll = psVectorAlloc(numOverscanImages, PS_TYPE_F32);
-    psListElem *tmpOverscan = (psListElem *) bias->head;
-    psS32 i = 0;
-    psF64 statValue;
-    //
-    // We loop through each overscan image, calculating the specified
-    // statistic on that image.
-    //
-    while (NULL != tmpOverscan) {
-        psImage *myOverscanImage = (psImage *) tmpOverscan->data;
-
-        PS_ASSERT_IMAGE_TYPE(myOverscanImage, PS_TYPE_F32, NULL);
-        myStats = psImageStats(myStats, myOverscanImage, NULL, (psMaskType)0xffffffff);
-        if (myStats == NULL) {
-            psError(PS_ERR_UNKNOWN, false, "psImageStats(): could not perform requested statistical operation.  Returning in image.\n");
-            psFree(statsAll);
-            return(false);
-        }
-        if (false == p_psGetStatValue(myStats, &statValue)) {
-            psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result from requested statistical operation.  Returning in image.\n");
-            psFree(statsAll);
-            return(false);
-        }
-        statsAll->data.F32[i] = statValue;
-        i++;
-        tmpOverscan = tmpOverscan->next;
-    }
-
-    //
-    // We reduce the individual stats for each overscan image to
-    // a single psF32.
-    //
-    myStats = psVectorStats(myStats, statsAll, NULL, NULL, 0);
-    if (myStats == NULL) {
-        psError(PS_ERR_UNKNOWN, false, "psImageStats(): could not perform requested statistical operation.  Returning in image.\n");
-        psFree(statsAll);
-        return(false);
-    }
-    if (false == p_psGetStatValue(myStats, &statValue)) {
-        psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result from requested statistical operation.  Returning in image.\n");
-        psFree(statsAll);
-        return(false);
-    }
-
-    //
-    // Subtract the result and return.
-    //
-    ImageSubtractScalar(in, statValue);
-    psFree(statsAll);
-    return(in);
-}
-
-/******************************************************************************
-ReduceOverscanImageToCol(overscanImage, myStats): This private routine reduces
-a single psImage to a column by combining all pixels from each row into a
-single pixel via requested statistic in myStats.
- *****************************************************************************/
-static psVector *ReduceOverscanImageToCol(
-    psImage *overscanImage,
-    psStats *myStats)
-{
-    psF64 statValue;
-    psVector *tmpRow = psVectorAlloc(overscanImage->numCols, PS_TYPE_F32);
-    psVector *tmpCol = psVectorAlloc(overscanImage->numRows, PS_TYPE_F32);
-
-    //
-    // For each row, we store all pixels in that row into a temporary psVector,
-    // then we run psVectorStats() on that vector.
-    //
-    for (psS32 i=0;i<overscanImage->numRows;i++) {
-        for (psS32 j=0;j<overscanImage->numCols;j++) {
-            tmpRow->data.F32[j] = overscanImage->data.F32[i][j];
-        }
-
-        psStats *rc = psVectorStats(myStats, tmpRow, NULL, NULL, 0);
-        if (rc == NULL) {
-            psError(PS_ERR_UNKNOWN, true, "psVectorStats() could not perform requested statistical operation.  Returning in image.\n");
-            return(NULL);
-        }
-
-        if (false ==  p_psGetStatValue(rc, &statValue)) {
-            psError(PS_ERR_UNKNOWN, true, "p_psGetStatValue() could not determine result from requested statistical operation.  Returning in image.\n");
-            return(NULL);
-        }
-
-        tmpCol->data.F32[i] = (psF32) statValue;
-    }
-    psFree(tmpRow);
-
-    return(tmpCol);
-}
-
-/******************************************************************************
-ReduceOverscanImageToCol(overscanImage, myStats): This private routine reduces
-a single psImage to a row by combining all pixels from each column into a
-single pixel via requested statistic in myStats.
- *****************************************************************************/
-static psVector *ReduceOverscanImageToRow(
-    psImage *overscanImage,
-    psStats *myStats)
-{
-    psF64 statValue;
-    psVector *tmpRow = psVectorAlloc(overscanImage->numCols, PS_TYPE_F32);
-    psVector *tmpCol = psVectorAlloc(overscanImage->numRows, PS_TYPE_F32);
-
-    //
-    // For each column, we store all pixels in that column into a temporary psVector,
-    // then we run psVectorStats() on that vector.
-    //
-    for (psS32 i=0;i<overscanImage->numCols;i++) {
-        for (psS32 j=0;j<overscanImage->numRows;j++) {
-            tmpCol->data.F32[j] = overscanImage->data.F32[j][i];
-        }
-
-        psStats *rc = psVectorStats(myStats, tmpCol, NULL, NULL, 0);
-        if (rc == NULL) {
-            psError(PS_ERR_UNKNOWN, true, "psVectorStats() could not perform requested statistical operation.  Returning in image.\n");
-            return(NULL);
-        }
-
-        if (false ==  p_psGetStatValue(rc, &statValue)) {
-            psError(PS_ERR_UNKNOWN, true, "p_psGetStatValue() could not determine result from requested statistical operation.  Returning in image.\n");
-            return(NULL);
-        }
-
-        tmpRow->data.F32[i] = (psF32) statValue;
-    }
-    psFree(tmpCol);
-
-    return(tmpRow);
-}
-
-/******************************************************************************
-OverscanReduce(vecSize, bias, myStats): This private routine takes a psList of
-overscan images (in bias) and reduces them to a single psVector via the
-specified psStats struct.  The vector is then scaled to the length or the
-row/column in inImg.
- *****************************************************************************/
-static psVector* OverscanReduce(
-    psImage *inImg,
-    pmOverscanAxis overScanAxis,
-    psList *bias,
-    void *fitSpec,
-    pmFit fit,
-    psStats *myStats)
-{
-    if ((overScanAxis != PM_OVERSCAN_ROWS) && (overScanAxis != PM_OVERSCAN_COLUMNS)) {
-        psError(PS_ERR_UNKNOWN, true, "overScanAxis must be PM_OVERSCAN_ROWS or PM_OVERSCAN_COLUMNS\n");
-        return(NULL);
-    }
-    PS_ASSERT_PTR_NON_NULL(inImg, NULL);
-    PS_ASSERT_PTR_NON_NULL(bias, NULL);
-    PS_ASSERT_PTR_NON_NULL(bias->head, NULL);
-    PS_ASSERT_PTR_NON_NULL(myStats, NULL);
-    //
-    // Allocate a psVector for the output of this routine.
-    //
-    psS32 vecSize = GetOverscanSize(inImg, overScanAxis);
-    psVector *overscanVector = psVectorAlloc(vecSize, PS_TYPE_F32);
-
-    //
-    // Allocate an array of psVectors with one psVector per element of the
-    // final oversan column vector.  These psVectors will be used with
-    // psStats to reduce the multiple elements from each overscan column
-    // vector to a single final column vector.
-    //
-    psS32 numOverscanImages = psListLength(bias);
-    psVector **overscanVectors = (psVector **) psAlloc(numOverscanImages * sizeof(psVector *));
-    for (psS32 i = 0 ; i < numOverscanImages ; i++) {
-        overscanVectors[i] = NULL;
-    }
-
-    //
-    // We iterate through the list of overscan images.  For each image,
-    // we reduce it to a single column or row.  Save the overscan vector
-    // in overscanVectors[].
-    //
-    psListElem *tmpOverscan = (psListElem *) bias->head;
-    psS32 overscanID = 0;
-    while (tmpOverscan != NULL) {
-        psImage *tmpOverscanImage = (psImage *) tmpOverscan->data;
-        if (overScanAxis == PM_OVERSCAN_ROWS) {
-            overscanVectors[overscanID] = ReduceOverscanImageToRow(tmpOverscanImage, myStats);
-        } else if (overScanAxis == PM_OVERSCAN_COLUMNS) {
-            overscanVectors[overscanID] = ReduceOverscanImageToCol(tmpOverscanImage, myStats);
-        }
-
-        tmpOverscan = tmpOverscan->next;
-        overscanID++;
-    }
-
-    //
-    // For each overscan vector, if necessary, we scale that column or
-    // row to vecSize.  Note: we should have already ensured that the
-    // fit is poly or spline.
-    //
-    for (psS32 i = 0 ; i < numOverscanImages ; i++) {
-        psVector *tmpOverscanVector = overscanVectors[i];
-
-        if (tmpOverscanVector->n != vecSize) {
-            overscanVectors[i] = ScaleOverscanVector(tmpOverscanVector, vecSize, fitSpec, fit);
-            if (overscanVectors[i] == NULL) {
-                psError(PS_ERR_UNKNOWN, false, "ScaleOverscanVector(): could not scale the overscan vector.\n");
-                for (psS32 i = 0 ; i < numOverscanImages ; i++) {
-                    psFree(overscanVectors[i]);
-                }
-                psFree(overscanVectors);
-                psFree(tmpOverscanVector);
-                return(NULL);
-            }
-            psFree(tmpOverscanVector);
-        }
-    }
-
-    //
-    // We collect all elements in the overscan vectors for the various
-    // overscan images into a single psVector (tmpVec).  Then we call
-    // psStats on that vector to determine the final values for the
-    // overscan vector.
-    //
-    psVector *tmpVec = psVectorAlloc(numOverscanImages, PS_TYPE_F32);
-    psF64 statValue;
-    for (psS32 i = 0 ; i < vecSize ; i++) {
-        // Collect the i-th elements from each overscan vector into a single vector.
-        for (psS32 j = 0 ; j < numOverscanImages ; j++) {
-            tmpVec->data.F32[j] = overscanVectors[j]->data.F32[i];
-        }
-
-        if (NULL == psVectorStats(myStats, tmpVec, NULL, NULL, 0)) {
-            psError(PS_ERR_UNKNOWN, false, "psVectorStats(): could not perform requested statistical operation.  Returning in image.\n");
-            for (psS32 i = 0 ; i < numOverscanImages ; i++) {
-                psFree(overscanVectors[i]);
-            }
-            psFree(overscanVectors);
-            psFree(tmpVec);
-            return(NULL);
-        }
-        if (false == p_psGetStatValue(myStats, &statValue)) {
-            psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result from requested statistical operation.  Returning in image.\n");
-            for (psS32 i = 0 ; i < numOverscanImages ; i++) {
-                psFree(overscanVectors[i]);
-            }
-            psFree(overscanVectors);
-            psFree(tmpVec);
-            return(NULL);
-        }
-
-        overscanVector->data.F32[i] = (psF32) statValue;
-    }
-
-    //
-    // We're done.  Free the intermediate overscan vectors.
-    //
-    psFree(tmpVec);
-    for (psS32 i = 0 ; i < numOverscanImages ; i++) {
-        psFree(overscanVectors[i]);
-    }
-    psFree(overscanVectors);
-
-    //
-    // Return the computed overscanVector
-    //
-    return(overscanVector);
-}
-
-/******************************************************************************
-RebinOverscanVector(overscanVector, nBinOrig, myStats): this private routine
-takes groups of nBinOrig elements in the input vector, combines them into a
-single pixel via myStats and psVectorStats(), and then outputs a vector of
-those pixels.
- *****************************************************************************/
-static psS32 RebinOverscanVector(
-    psVector *overscanVector,
-    psS32 nBinOrig,
-    psStats *myStats)
-{
-    psF64 statValue;
-    psS32 nBin;
-    if ((nBinOrig > 1) && (nBinOrig < overscanVector->n)) {
-        psS32 numBins = 1+((overscanVector->n)/nBinOrig);
-        psVector *myBin = psVectorAlloc(numBins, PS_TYPE_F32);
-        psVector *binVec = psVectorAlloc(nBinOrig, PS_TYPE_F32);
-
-        for (psS32 i=0;i<numBins;i++) {
-            for(psS32 j=0;j<nBinOrig;j++) {
-                if (overscanVector->n > ((i*nBinOrig)+j)) {
-                    binVec->data.F32[j] = overscanVector->data.F32[(i*nBinOrig)+j];
-                } else {
-                    // XXX: we get here if nBinOrig does not evenly divide
-                    // the overscanVector vector.  This is the last bin.  Should
-                    // we change the binVec->n to acknowledge that?
-                    binVec->n = j;
-                }
-            }
-            psStats *rc = psVectorStats(myStats, binVec, NULL, NULL, 0);
-            if (rc == NULL) {
-                psError(PS_ERR_UNKNOWN, false, "psVectorStats(): could not perform requested statistical operation.  Returning in image.\n");
-                return(-1);
-            }
-            if (false ==  p_psGetStatValue(rc, &statValue)) {
-                psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result from requested statistical operation.  Returning in image.\n");
-                return(-1);
-            }
-            myBin->data.F32[i] = statValue;
-        }
-
-        // Change the effective size of overscanVector.
-        overscanVector->n = numBins;
-        for (psS32 i=0;i<numBins;i++) {
-            overscanVector->data.F32[i] = myBin->data.F32[i];
-        }
-        psFree(binVec);
-        psFree(myBin);
-        nBin = nBinOrig;
-    } else {
-        nBin = 1;
-    }
-
-    return(nBin);
-}
-
-/******************************************************************************
-FitOverscanVectorAndUnbin(inImg, overscanVector, overScanAxis, fitSpec, fit,
-nBin):  this private routine fits a psPolynomial or psSpline to the overscan
-vector.  It then creates a new vector, with a size determined by the input
-image, evaluates the psPolynomial or psSpline at each element in that vector,
-then returns that vector.
- *****************************************************************************/
-static psVector *FitOverscanVectorAndUnbin(
-    psImage *inImg,
-    psVector *overscanVector,
-    pmOverscanAxis overScanAxis,
-    void *fitSpec,
-    pmFit fit,
-    psS32 nBin)
-{
-    psPolynomial1D* myPoly = NULL;
-    psSpline1D *mySpline = NULL;
-    //
-    // Fit a polynomial or spline to the overscan vector.
-    //
-    if (fit == PM_FIT_POLYNOMIAL) {
-        myPoly = (psPolynomial1D *) fitSpec;
-        PS_ASSERT_POLY_NON_NULL(myPoly, NULL);
-        PS_ASSERT_POLY1D(myPoly, NULL);
-        myPoly = psVectorFitPolynomial1D(myPoly, NULL, 0, overscanVector, NULL, NULL);
-        if (myPoly == NULL) {
-            psError(PS_ERR_UNKNOWN, false, "Could not fit a polynomial to overscan vector.  Returning NULL.\n");
-            return(NULL);
-        }
-    } else if (fit == PM_FIT_SPLINE) {
-        mySpline = psVectorFitSpline1D(NULL, overscanVector);
-        if (mySpline == NULL) {
-            psError(PS_ERR_UNKNOWN, false, "Could not fit a spline to overscan vector.  Returning NULL.\n");
-            return(NULL);
-        }
-        if (fitSpec != NULL) {
-            // Copy the resulting spline fit into fitSpec.
-            psSpline1D *ptrSpline = (psSpline1D *) fitSpec;
-            PS_ASSERT_SPLINE(ptrSpline, NULL);
-            SplineCopy(ptrSpline, mySpline);
-        }
-    }
-
-    //
-    // Evaluate the poly/spline at each pixel in the overscan row/column.
-    //
-    psS32 vecSize = GetOverscanSize(inImg, overScanAxis);
-    psVector *newVec = psVectorAlloc(vecSize, PS_TYPE_F32);
-    if ((nBin > 1) && (nBin < overscanVector->n)) {
-        for (psS32 i = 0 ; i < vecSize ; i++) {
-            if (fit == PM_FIT_POLYNOMIAL) {
-                newVec->data.F32[i] = psPolynomial1DEval(myPoly, ((psF32) i) / ((psF32) nBin));
-            } else if (fit == PM_FIT_SPLINE) {
-                newVec->data.F32[i] = psSpline1DEval(mySpline, ((psF32) i) / ((psF32) nBin));
-            }
-        }
-    } else {
-        for (psS32 i = 0 ; i < vecSize ; i++) {
-            if (fit == PM_FIT_POLYNOMIAL) {
-                newVec->data.F32[i] = psPolynomial1DEval(myPoly, (psF32) i);
-            } else if (fit == PM_FIT_SPLINE) {
-                newVec->data.F32[i] = psSpline1DEval(mySpline, (psF32) i);
-            }
-        }
-    }
-
-    psFree(mySpline);
-    psFree(overscanVector);
-    return(newVec);
-}
-
-
-
-/******************************************************************************
-UnbinOverscanVector(inImg, overscanVector, overScanAxis, nBin):  this private
-routine takes a psVector overscanVector that was previously binned by a factor
-of nBin, and then expands it to its original size, duplicated elements nBin
-times for each element in the input vector overscanVector.
- *****************************************************************************/
-static psVector *UnbinOverscanVector(
-    psImage *inImg,
-    psVector *overscanVector,
-    pmOverscanAxis overScanAxis,
-    psS32 nBin)
-{
-    psS32 vecSize = 0;
-
-    if (overScanAxis == PM_OVERSCAN_ROWS) {
-        vecSize = inImg->numCols;
-    } else if (overScanAxis == PM_OVERSCAN_COLUMNS) {
-        vecSize = inImg->numRows;
-    }
-
-    psVector *newVec = psVectorAlloc(vecSize, PS_TYPE_F32);
-    for (psS32 i = 0 ; i < vecSize ; i++) {
-        newVec->data.F32[i] = overscanVector->data.F32[i/nBin];
-    }
-
-    psFree(overscanVector);
-    return(newVec);
-}
-
-
-/******************************************************************************
-SubtractVectorFromImage(inImg, overscanVector, overScanAxis):  this private
-routine subtracts the overscanVector column-wise or row-wise from inImg.
- *****************************************************************************/
-static psImage *SubtractVectorFromImage(
-    psImage *inImg,
-    psVector *overscanVector,
-    pmOverscanAxis overScanAxis)
-{
-    //
-    // Subtract overscan vector row-wise from the image.
-    //
-    if (overScanAxis == PM_OVERSCAN_ROWS) {
-        for (psS32 i=0;i<inImg->numCols;i++) {
-            for (psS32 j=0;j<inImg->numRows;j++) {
-                inImg->data.F32[j][i]-= overscanVector->data.F32[i];
-            }
-        }
-    }
-
-    //
-    // Subtract overscan vector column-wise from the image.
-    //
-    if (overScanAxis == PM_OVERSCAN_COLUMNS) {
-        for (psS32 i=0;i<inImg->numRows;i++) {
-            for (psS32 j=0;j<inImg->numCols;j++) {
-                inImg->data.F32[i][j]-= overscanVector->data.F32[i];
-            }
-        }
-    }
-
-    return(inImg);
-}
-
-
-
-typedef enum {
-    PM_ERROR_NO_SUBTRACTION,
-    PM_WARNING_NO_SUBTRACTION,
-    PM_ERROR_NO_BIAS_SUBTRACT,
-    PM_WARNING_NO_BIAS_SUBTRACT,
-    PM_ERROR_NO_DARK_SUBTRACT,
-    PM_WARNING_NO_DARK_SUBTRACT,
-    PM_OKAY
-} pmSubtractBiasAssertStatus;
-/******************************************************************************
-AssertCodeOverscan(....) this private routine verifies that the various input
-parameters to pmSubtractBias() are correct for overscan subtraction.
- *****************************************************************************/
-pmSubtractBiasAssertStatus AssertCodeOverscan(
-    pmReadout *in,
-    void *fitSpec,
-    pmFit fit,
-    bool overscan,
-    psStats *stat,
-    int nBinOrig,
-    const pmReadout *bias,
-    const pmReadout *dark)
-{
-
-    PS_ASSERT_READOUT_NON_NULL(in, PM_ERROR_NO_SUBTRACTION);
-    PS_ASSERT_READOUT_NON_EMPTY(in, PM_ERROR_NO_SUBTRACTION);
-    PS_ASSERT_READOUT_TYPE(in, PS_TYPE_F32, PM_ERROR_NO_SUBTRACTION);
-    PS_WARN_PTR_NON_NULL(in->parent);
-    if (in->parent != NULL) {
-        PS_WARN_PTR_NON_NULL(in->parent->concepts);
-    }
-
-    if (overscan == true) {
-        pmOverscanAxis overScanAxis = GetOverscanAxis(in);
-        PS_ASSERT_PTR_NON_NULL(stat, PM_ERROR_NO_SUBTRACTION);
-        PS_ASSERT_PTR_NON_NULL(in->bias, PM_ERROR_NO_SUBTRACTION);
-        PS_ASSERT_PTR_NON_NULL(in->bias->head, PM_ERROR_NO_SUBTRACTION);
-        //
-        // Check the type, size of each bias image.
-        //
-        psListElem *tmpOverscan = (psListElem *) in->bias->head;
-        psS32 numOverscans = 0;
-        while (NULL != tmpOverscan) {
-            numOverscans++;
-            psImage *myOverscanImage = (psImage *) tmpOverscan->data;
-            PS_ASSERT_IMAGE_TYPE(myOverscanImage, PS_TYPE_F32, PM_ERROR_NO_SUBTRACTION);
-            // XXX: Get this right with the rows and columns.
-            if (overScanAxis == PM_OVERSCAN_ROWS) {
-                if (myOverscanImage->numRows != in->image->numRows) {
-                    psLogMsg(__func__, PS_LOG_WARN,
-                             "WARNING: pmSubtractBias.(): overscan image (# %d) has %d rows, input image has %d rows\n",
-                             numOverscans, myOverscanImage->numCols, in->image->numRows);
-                    if (fit == PM_FIT_NONE) {
-                        psError(PS_ERR_UNKNOWN, true, "Don't know how to scale the overscan vectors.  Set fit to PM_FIT_POLYNOMIAL or PM_FIT_SPLINE.\n");
-                        return(PM_ERROR_NO_SUBTRACTION);
-                    }
-                }
-            } else if (overScanAxis == PM_OVERSCAN_COLUMNS) {
-                if (myOverscanImage->numCols != in->image->numCols) {
-                    psLogMsg(__func__, PS_LOG_WARN,
-                             "WARNING: pmSubtractBias.(): overscan image (# %d) has %d columns, input image has %d columns\n",
-                             numOverscans, myOverscanImage->numCols, in->image->numCols);
-                    if (fit == PM_FIT_NONE) {
-                        psError(PS_ERR_UNKNOWN, true, "Don't know how to scale the overscan vectors.  Set fit to PM_FIT_POLYNOMIAL or PM_FIT_SPLINE.\n");
-                        return(PM_ERROR_NO_SUBTRACTION);
-                    }
-                }
-            } else if (overScanAxis != PM_OVERSCAN_ALL) {
-                psError(PS_ERR_UNKNOWN, true, "Must specify and overscan axis.\n");
-                return(PM_ERROR_NO_SUBTRACTION);
-            }
-            tmpOverscan = tmpOverscan->next;
-        }
-    } else {
-        if (fit != PM_FIT_NONE) {
-            psLogMsg(__func__, PS_LOG_WARN,
-                     "WARNING: pmSubtractBias.(): overscan is FALSE and fit is not PM_FIT_NONE.\n");
-            return(PM_WARNING_NO_SUBTRACTION);
-        }
-    }
-
-    // XXX: I do not like the following spec since it's useless to specify
-    // a psSpline as the fitSpec.
-    if (0) {
-        if ((fitSpec == NULL) &&
-                ((fit != PM_FIT_NONE) || (overscan == true))) {
-            psError(PS_ERR_UNKNOWN, true, "fitSpec is NULL and fit is not PM_FIT_NONE or overscan is TRUE.\n");
-            return(PM_ERROR_NO_SUBTRACTION);
-        }
-    }
-
-    return(PM_OKAY);
-}
-
-/******************************************************************************
-AssertCodeBias(....) this private routine verifies that the various input
-parameters to pmSubtractBias() are correct for bias subtraction.
- *****************************************************************************/
-static pmSubtractBiasAssertStatus AssertCodeBias(
-    pmReadout *in,
-    void *fitSpec,
-    pmFit fit,
-    bool overscan,
-    psStats *stat,
-    int nBinOrig,
-    const pmReadout *bias,
-    const pmReadout *dark)
-{
-    if ((in->image->numRows + in->row0 - bias->row0) > bias->image->numRows) {
-        psError(PS_ERR_UNKNOWN,true, "bias image does not have enough rows.  Returning in image\n");
-        return(PM_ERROR_NO_BIAS_SUBTRACT);
-    }
-    if ((in->image->numCols + in->col0 - bias->col0) > bias->image->numCols) {
-        psError(PS_ERR_UNKNOWN,true, "bias image does not have enough columns.  Returning in image\n");
-        return(PM_ERROR_NO_BIAS_SUBTRACT);
-    }
-
-    if (bias != NULL) {
-        PS_ASSERT_READOUT_NON_EMPTY(bias, PM_ERROR_NO_BIAS_SUBTRACT);
-        PS_ASSERT_READOUT_TYPE(bias, PS_TYPE_F32, PM_ERROR_NO_DARK_SUBTRACT);
-    }
-    return(PM_OKAY);
-}
-
-/******************************************************************************
-AssertCodeDark(....) this private routine verifies that the various input
-parameters to pmSubtractBias() are correct for dark subtraction.
- *****************************************************************************/
-pmSubtractBiasAssertStatus AssertCodeDark(
-    pmReadout *in,
-    void *fitSpec,
-    pmFit fit,
-    bool overscan,
-    psStats *stat,
-    int nBinOrig,
-    const pmReadout *bias,
-    const pmReadout *dark)
-{
-    if ((in->image->numRows + in->row0 - dark->row0) > dark->image->numRows) {
-        psError(PS_ERR_UNKNOWN, true, "dark image does not have enough rows.  Returning in image\n");
-        return(PM_ERROR_NO_DARK_SUBTRACT);
-    }
-    if ((in->image->numCols + in->col0 - dark->col0) > dark->image->numCols) {
-        psError(PS_ERR_UNKNOWN, true, "dark image does not have enough columns.  Returning in image\n");
-        return(PM_ERROR_NO_DARK_SUBTRACT);
-    }
-
-    if (dark != NULL) {
-        PS_ASSERT_READOUT_NON_EMPTY(dark, PM_ERROR_NO_DARK_SUBTRACT);
-        PS_ASSERT_READOUT_TYPE(dark, PS_TYPE_F32, PM_ERROR_NO_DARK_SUBTRACT);
-    }
-    return(PM_OKAY);
-}
-
-/******************************************************************************
-p_psDetermineTrimmedImage(): global routine: determines the region of the
-input pmReadout which will be operated on by the various detrend modules.  It
-does a metadata fetch on "CELL.TRIMSEC" for the parent cell of the pmReadout.
- 
-Use it this way:
-    PS_WARN_PTR_NON_NULL(in->parent);
-    if (in->parent != NULL) {
-        PS_WARN_PTR_NON_NULL(in->parent->concepts);
-    }
-    //
-    // Determine trimmed image from metadata.
-    //
-    psImage *trimmedImg = p_psDetermineTrimmedImage(in);
- 
-XXX: Create a pmUtils.c file and put this routine there.
- *****************************************************************************/
-psImage *p_psDetermineTrimmedImage(pmReadout *in)
-{
-    if ((in->parent == NULL) || (in->parent->concepts == NULL)) {
-        psLogMsg(__func__, PS_LOG_WARN,
-                 "WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).\n");
-        return(in->image);
-    }
-
-    psBool rc = false;
-    psImage *trimmedImg = NULL;
-    psRegion *trimRegion = psMetadataLookupPtr(&rc, in->parent->concepts,
-                           "CELL.TRIMSEC");
-    if (rc == false) {
-        psLogMsg(__func__, PS_LOG_WARN,
-                 "WARNING: could not determine CELL.TRIMSEC from parent cell Metadata.\n");
-        trimmedImg = in->image;
-    } else {
-        trimmedImg = psImageSubset(in->image, *trimRegion);
-    }
-
-    return(trimmedImg);
-}
-
-
-/******************************************************************************
-pmSubtractBias(....): see SDRS for complete specification.
- 
-XXX: Code and assert type support: U16, S32, F32.
-XXX: Add trace messages.
- *****************************************************************************/
-pmReadout *pmSubtractBias(
-    pmReadout *in,
-    void *fitSpec,
-    pmFit fit,
-    bool overscan,
-    psStats *stat,
-    int nBin,
-    const pmReadout *bias,
-    const pmReadout *dark)
+pmReadout *pmSubtractBias(pmReadout *in, pmOverscanOptions *overscanOpts,
+                          const pmReadout *bias, const pmReadout *dark)
 {
     psTrace(".psModule.pmSubtracBias.pmSubtractBias", 4,
             "---- pmSubtractBias() begin ----\n");
-    //
-    // Check input parameters, generate warnings and errors.
-    //
-    if (PM_OKAY != AssertCodeOverscan(in, fitSpec, fit, overscan, stat, nBin, bias, dark)) {
-        return(in);
-    }
-    //
-    // Determine trimmed image from metadata.
-    //
-    psImage *trimmedImg = p_psDetermineTrimmedImage(in);
-
-    //
-    // Subtract overscan frames if necessary.
-    //
-    if (overscan == true) {
-        pmOverscanAxis overScanAxis = GetOverscanAxis(in);
-        //
-        //  Create a psStats data structure and determine the highest
-        //  priority stats option.
-        //
-        psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
-        if (stat != NULL) {
-            myStats->options = GenNewStatOptions(stat);
-        }
-
-        //
-        // Reduce overscan images to a single pixel, then subtract.
-        // This code is no longer required as of SDRS 12-09.
-        //
-        if (overScanAxis == PM_OVERSCAN_ALL) {
-            if (false == OverscanReducePixel(trimmedImg, in->bias, myStats)) {
+    PS_ASSERT_READOUT_NON_NULL(in, NULL);
+    PS_ASSERT_READOUT_NON_EMPTY(in, NULL);
+    PS_ASSERT_READOUT_TYPE(in, PS_TYPE_F32, NULL);
+
+    psImage *image = in->image;         // The input image
+
+    // Overscan processing
+    if (overscanOpts) {
+        // Check for an unallowable pmFit.
+        if (overscanOpts->fitType != PM_FIT_NONE && overscanOpts->fitType != PM_FIT_POLY_ORD &&
+                overscanOpts->fitType != PM_FIT_POLY_CHEBY && overscanOpts->fitType != PM_FIT_SPLINE) {
+            psError(PS_ERR_UNKNOWN, true, "Invalid fit type (%d).  Returning original image.\n", overscanOpts->fitType);
+            return(in);
+        }
+
+        psList *overscans = in->bias; // List of the overscan images
+
+        psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN); // A new psStats, to avoid clobbering original
+        myStats->options = GenNewStatOptions(overscanOpts->stat);
+
+        // Reduce all overscan pixels to a single value
+        if (overscanOpts->single) {
+            psVector *pixels = psVectorAlloc(0, PS_TYPE_F32);
+            pixels->n = 0;
+            psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+            psImage *overscan = NULL;   // Overscan image from iterator
+            while ((overscan = psListGetAndIncrement(iter))) {
+                int index = pixels->n;  // Index
+                pixels = psVectorRealloc(pixels, pixels->n + overscan->numRows * overscan->numCols);
+                // XXX Reimplement with memcpy
+                for (int i = 0; i < overscan->numRows; i++) {
+                    for (int j = 0; j < overscan->numCols; j++) {
+                        pixels->data.F32[index++] = overscan->data.F32[i][j];
+                    }
+                }
+
+            }
+            psFree(iter);
+
+            (void)psVectorStats(myStats, pixels, NULL, NULL, 0);
+            double reduced = NAN;     // Result of statistics
+            if (! p_psGetStatValue(myStats, &reduced)) {
+                psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result from requested statistical operation.  Returning input image.\n");
                 return(in);
             }
-            psFree(myStats);
+            (void)psBinaryOp(image, image, "-", psScalarAlloc((float)reduced, PS_TYPE_F32));
         } else {
-            //
-            // Reduce the overscan images to a single overscan vector.
-            //
-            psVector *overscanVector = OverscanReduce(in->image, overScanAxis,
-                                       in->bias, fitSpec,
-                                       fit, myStats);
-            if (overscanVector == NULL) {
-                psError(PS_ERR_UNKNOWN, false, "Could not reduce overscan images to a single overscan vector.  Returning in image\n");
-                psFree(myStats);
-                return(in);
+
+            // We do the regular overscan subtraction
+
+            bool readRows = psMetadataLookupBool(NULL, in->parent->concepts, "CELL.READDIR");// Read direction
+
+            if (readRows) {
+                // The read direction is rows
+                psArray *pixels = psArrayAlloc(image->numRows); // Array of vectors containing pixels
+                for (int i = 0; i < pixels->n; i++) {
+                    psVector *values = psVectorAlloc(0, PS_TYPE_F32);
+                    values->n = 0;
+                    pixels->data[i] = values;
+                }
+
+                // Pull the pixels out into the vectors
+                psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+                psImage *overscan = NULL; // Overscan image from iterator
+                while ((overscan = psListGetAndIncrement(iter))) {
+                    int diff = image->row0 - overscan->row0; // Offset between the two regions
+                    for (int i = MAX(0,diff); i < MIN(image->numRows, overscan->numRows + diff); i++) {
+                        // i is row on overscan
+                        // XXX Reimplement with memcpy
+                        psVector *values = pixels->data[i];
+                        int index = values->n; // Index in the vector
+                        values = psVectorRealloc(values, values->n + overscan->numCols);
+                        for (int j = 0; j < overscan->numCols; j++) {
+                            values->data.F32[index++] = overscan->data.F32[i][j];
+                        }
+                        values->n += overscan->numCols;
+                        pixels->data[i] = values; // Update the pointer in case it's moved
+                    }
+                }
+                psFree(iter);
+
+                // Reduce the overscans
+                psVector *reduced = overscanVector(overscanOpts, pixels, myStats);
+                psFree(pixels);
+                if (! reduced) {
+                    return in;
+                }
+
+                // Subtract row by row
+                for (int i = 0; i < image->numRows; i++) {
+                    for (int j = 0; j < image->numCols; j++) {
+                        image->data.F32[i][j] -= reduced->data.F32[i];
+                    }
+                }
+                psFree(reduced);
+
+            } else {
+                // The read direction is columns
+                psArray *pixels = psArrayAlloc(image->numCols); // Array of vectors containing pixels
+                for (int i = 0; i < pixels->n; i++) {
+                    psVector *values = psVectorAlloc(0, PS_TYPE_F32);
+                    values->n = 0;
+                    pixels->data[i] = values;
+                }
+
+                // Pull the pixels out into the vectors
+                psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+                psImage *overscan = NULL; // Overscan image from iterator
+                while ((overscan = psListGetAndIncrement(iter))) {
+                    int diff = image->col0 - overscan->col0; // Offset between the two regions
+                    for (int i = MAX(0,diff); i < MIN(image->numCols, overscan->numCols + diff); i++) {
+                        // i is column on overscan
+                        // XXX Reimplement with memcpy
+                        psVector *values = pixels->data[i];
+                        int index = values->n; // Index in the vector
+                        values = psVectorRealloc(values, values->n + overscan->numRows);
+                        for (int j = 0; j < overscan->numRows; j++) {
+                            values->data.F32[index++] = overscan->data.F32[i][j];
+                        }
+                        values->n += overscan->numRows;
+                        pixels->data[i] = values; // Update the pointer in case it's moved
+                    }
+                }
+                psFree(iter);
+
+                // Reduce the overscans
+                psVector *reduced = overscanVector(overscanOpts, pixels, myStats);
+                psFree(pixels);
+                if (! reduced) {
+                    return in;
+                }
+
+                // Subtract column by column
+                for (int i = 0; i < image->numCols; i++) {
+                    for (int j = 0; j < image->numRows; j++) {
+                        image->data.F32[j][i] -= reduced->data.F32[i];
+                    }
+                }
+                psFree(reduced);
             }
-
-            //
-            // Rebin the overscan vector if necessary.
-            //
-            psS32 newBin = RebinOverscanVector(overscanVector, nBin, myStats);
-            if (newBin < 0) {
-                psError(PS_ERR_UNKNOWN, false, "Could rebin the overscan vector.  Returning in image\n");
-                psFree(myStats);
-                return(in);
-            }
-
-            //
-            // If necessary, fit a psPolynomial or psSpline to the overscan vector.
-            // Then, unbin the overscan vector to appropriate length for the in image.
-            //
-            if ((fit == PM_FIT_POLYNOMIAL) || (fit == PM_FIT_SPLINE)) {
-                overscanVector = FitOverscanVectorAndUnbin(trimmedImg, overscanVector, overScanAxis, fitSpec, fit, newBin);
-                if (overscanVector == NULL) {
-                    psError(PS_ERR_UNKNOWN, false, "Could not fit the polynomial or spline to the overscan vector.  Returning in image\n");
-                    psFree(myStats);
-                    return(in);
-                }
-            } else {
-                overscanVector = UnbinOverscanVector(trimmedImg, overscanVector, overScanAxis, newBin);
-            }
-
-            //
-            // Subtract the overscan vector from the input image.
-            //
-            SubtractVectorFromImage(trimmedImg, overscanVector, overScanAxis);
-            psFree(myStats);
-            psFree(overscanVector);
-        }
-    }
-
-    //
-    // Perform bias subtraction if necessary.
-    //
-    if (bias != NULL) {
-        if (PM_OKAY == AssertCodeBias(in, fitSpec, fit, overscan, stat, nBin, bias, dark)) {
-            SubtractFrame(in, bias);
-        }
-    }
-
-    //
-    // Perform dark subtraction if necessary.
-    //
-    if (dark != NULL) {
-        if (PM_OKAY == AssertCodeDark(in, fitSpec, fit, overscan, stat, nBin, bias, dark)) {
-            psBool rc;
-            psF32 scale = 0.0;
-            if (in->parent != NULL) {
-                scale = psMetadataLookupS32(&rc, in->parent->concepts, "CELL.DARKTIME");
-                if (rc == false) {
-                    psLogMsg(__func__, PS_LOG_WARN,
-                             "WARNING: pmSubtractBias.(): could not determine CELL.FARKTIME from in->parent metadata.\n");
-                }
-            }
-            SubtractDarkFrame(in, dark, scale);
-        }
-    }
-
-    //
-    // All done.
-    //
-    psTrace(".psModule.pmSubtracBias.pmSubtractBias", 4,
-            "---- pmSubtractBias() exit ----\n");
-    return(in);
-}
-
-
+        }
+        psFree(myStats);
+    } // End of overscan subtraction
+
+    // Bias frame subtraction
+    if (bias) {
+        SubtractFrame(in, bias, 1.0);
+    }
+
+    if (dark) {
+        // Get the scaling
+        float inTime = psMetadataLookupF32(NULL, in->parent->concepts, "CELL.DARKTIME");
+        float darkTime = psMetadataLookupF32(NULL, dark->parent->concepts, "CELL.DARKTIME");
+        SubtractFrame(in, dark, inTime/darkTime);
+    }
+
+    return in;
+}
+
+
Index: /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractBias.h
===================================================================
--- /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractBias.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractBias.h	(revision 6448)
@@ -1,2 +1,7 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
 /** @file  pmSubtractBias.h
  *
@@ -6,6 +11,6 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-11-23 23:54:30 $
+ *  @version $Revision: 1.4.12.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -23,8 +28,10 @@
 #include<math.h>
 #include "pslib.h"
-#include "pmAstrometry.h"
+
+#include "pmFPA.h"
 
 typedef enum {
     PM_OVERSCAN_NONE,                         ///< No overscan subtraction
+    PM_OVERSCAN_EDGE,                         ///< Subtract the statistic of pixels along the to-be-determined readout direction
     PM_OVERSCAN_ROWS,                         ///< Subtract rows
     PM_OVERSCAN_COLUMNS,                      ///< Subtract columns
@@ -33,30 +40,38 @@
 
 typedef enum {
-    PM_FIT_NONE,                              ///< No fit
-    PM_FIT_POLYNOMIAL,                        ///< Fit polynomial
-    PM_FIT_SPLINE                             ///< Fit cubic splines
+    PM_FIT_NONE,                        ///< No fit
+    PM_FIT_POLY_ORD,                    ///< Fit ordinary polynomial
+    PM_FIT_POLY_CHEBY,                  ///< Fit Chebyshev polynomial
+    PM_FIT_SPLINE                       ///< Fit cubic splines
 } pmFit;
 
-pmReadout *pmSubtractBias(
-    pmReadout *in,                      ///< The input pmReadout image
-    void *fitSpec,                      ///< A polynomial or spline, defining the fit type.
-    pmFit fit,                          ///< PM_FIT_SPLINE, PM_FIT_POLYNOMIAL, or PM_FIT_NONE
-    bool overscan,
-    psStats *stat,                      ///< The statistic to be used in combining overscan data
-    int nBin,                           ///< The amount of binning to be done image pixels.
-    const pmReadout *bias,              ///< A possibly NULL bias pmReadout which is to be subtracted
-    const pmReadout *dark               ///< A possibly NULL bias pmReadout which is to be subtracted
-);
+typedef struct
+{
+    // Inputs
+    bool single;                // Reduce all overscan regions to a single value?
+    pmFit fitType;              // Type of fit to overscan
+    unsigned int order;         // Order of polynomial, or number of spline pieces
+    psStats *stat;              // Statistic to use when reducing the minor direction
+    // Outputs
+    psPolynomial1D *poly;       // Result of polynomial fit
+    psSpline1D *spline;         // Result of spline fit
+}
+pmOverscanOptions;
 
-/******************************************************************************
-DetermineTrimmedImageg() This private routine determines the region of the
-input pmReadout which will be operated on by the various detrend modules.  It
-does a metadata fetch on "CELL.TRIMSEC" for the parent cell of the pmReadout.
- 
-XXX: Consider making a pmUtils.c file and put this routine there.
- *****************************************************************************/
-psImage *p_psDetermineTrimmedImage(
-    pmReadout *in
-);
+pmOverscanOptions *pmOverscanOptionsAlloc(bool single, pmFit fitType, unsigned int order, psStats *stat);
+
+pmReadout *pmSubtractBias(pmReadout *in, pmOverscanOptions *overscanOpts,
+                          const pmReadout *bias, const pmReadout *dark);
+
+#if 0
+pmReadout *pmSubtractBias(pmReadout *in,                ///< The input pmReadout image
+                          void *fitSpec,                ///< A polynomial or spline, defining the fit type.
+                          const psList *overscans,      ///< A psList of overscan images
+                          pmOverscanAxis overScanAxis,  ///< Defines how overscans are applied
+                          psStats *stat,                ///< The statistic to be used in combining overscan data
+                          int nBin,                     ///< The amount of binning to be done image pixels.
+                          pmFit fit,                    ///< PM_FIT_SPLINE, PM_FIT_POLYNOMIAL, or PM_FIT_NONE
+                          const pmReadout *bias);       ///< A possibly NULL bias pmReadout which is to be subtracted
+#endif
 
 #endif
Index: /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractSky.h
===================================================================
--- /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractSky.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/imsubtract/pmSubtractSky.h	(revision 6448)
@@ -6,6 +6,6 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-09-28 20:43:52 $
+ *  @version $Revision: 1.1.22.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -23,5 +23,5 @@
 #include<math.h>
 #include "pslib.h"
-#include "pmAstrometry.h"
+#include "pmFPA.h"
 
 // XXX: this is pmFit in pmSubtractBias.c, named psFit here.
Index: /branches/rel10_ifa/psModules/src/objects/Makefile.am
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/Makefile.am	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/Makefile.am	(revision 6448)
@@ -8,4 +8,5 @@
     pmPSFtry.c \
     pmModelGroup.c \
+    pmGrowthCurve.c \
     psEllipse.c
 
@@ -22,3 +23,4 @@
     pmPSFtry.h \
     pmModelGroup.h \
+    pmGrowthCurve.h \
     psEllipse.h
Index: /branches/rel10_ifa/psModules/src/objects/models/pmModel_PGAUSS.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 6448)
@@ -26,13 +26,13 @@
 
     if (deriv != NULL) {
-        // note difference from a pure gaussian: q = PAR[1]*r
+        psF32 *dPAR = deriv->data.F32;
         psF32 q = PAR[1]*r*r*t;
-        deriv->data.F32[0] = +1.0;
-        deriv->data.F32[1] = +r;
-        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
-        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
-        deriv->data.F32[4] = -2.0*q*px*X;
-        deriv->data.F32[5] = -2.0*q*py*Y;
-        deriv->data.F32[6] = -q*X*Y;
+        dPAR[0] = +1.0;
+        dPAR[1] = +r;
+        dPAR[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        dPAR[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        dPAR[4] = -2.0*q*px*X;
+        dPAR[5] = -2.0*q*py*Y;
+        dPAR[6] = -q*X*Y;
     }
     return(f);
@@ -47,5 +47,5 @@
 
     beta_lim[0][0].data.F32[0] = 1000;
-    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[1] = 3e6;
     beta_lim[0][0].data.F32[2] = 5;
     beta_lim[0][0].data.F32[3] = 5;
@@ -63,5 +63,5 @@
 
     params_max[0][0].data.F32[0] = 1e5;
-    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[1] = 1e8;
     params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
     params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
@@ -130,4 +130,5 @@
     params[5] = 1.2 / moments->Sy;
     params[6] = 0.0;
+
     return(true);
 }
Index: /branches/rel10_ifa/psModules/src/objects/models/pmModel_QGAUSS.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 6448)
@@ -31,16 +31,18 @@
 
     if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+
         // note difference from a pure gaussian: q = params->data.F32[1]*r
         psF32 t = PAR[1]*r*r;
         psF32 q = t*(PAR[7] + 2.25*pow(z, 1.25));
 
-        deriv->data.F32[0] = +1.0;
-        deriv->data.F32[1] = +r;
-        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
-        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
-        deriv->data.F32[4] = -2.0*q*px*X;
-        deriv->data.F32[5] = -2.0*q*py*Y;
-        deriv->data.F32[6] = -q*X*Y;
-        deriv->data.F32[7] = -t*z;
+        dPAR[0] = +1.0;
+        dPAR[1] = +r;
+        dPAR[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        dPAR[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        dPAR[4] = -2.0*q*px*X;
+        dPAR[5] = -2.0*q*py*Y;
+        dPAR[6] = -q*X*Y;
+        dPAR[7] = -t*z;
     }
     return(f);
@@ -55,5 +57,5 @@
 
     beta_lim[0][0].data.F32[0] = 1000;
-    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[1] = 3e6;
     beta_lim[0][0].data.F32[2] = 5;
     beta_lim[0][0].data.F32[3] = 5;
@@ -73,5 +75,5 @@
 
     params_max[0][0].data.F32[0] = 1e5;
-    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[1] = 1e8;
     params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
     params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
@@ -147,5 +149,5 @@
 
     // we can do this much better with intelligent choices here
-    for (z = 0.0; z < 20.0; z += dz) {
+    for (z = 0.0; z < 30.0; z += dz) {
         f = 1.0 / (1 + PAR[7]*z + pow(z, 2.25));
         if (f < limit)
@@ -198,6 +200,6 @@
     status &= ((dPAR[1]/PAR[1]) < 0.5);
 
-    if (status)
-        return true;
-    return false;
-}
+    if (!status)
+        return false;
+    return true;
+}
Index: /branches/rel10_ifa/psModules/src/objects/models/pmModel_SGAUSS.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 6448)
@@ -261,5 +261,5 @@
 psF64 pmModelRadius_SGAUSS  (const psVector *params, psF64 flux)
 {
-    psF64 r, z, pr, f;
+    psF64 r, z = 0.0, pr, f;
     psF32 *PAR = params->data.F32;
 
Index: /branches/rel10_ifa/psModules/src/objects/pmGrowthCurve.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmGrowthCurve.c	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/objects/pmGrowthCurve.c	(revision 6448)
@@ -0,0 +1,116 @@
+#include <pslib.h>
+# include "pmGrowthCurve.h"
+
+static void pmGrowthCurveFree (pmGrowthCurve *growth)
+{
+
+    if (growth == NULL)
+        return;
+
+    psFree (growth->radius);
+    psFree (growth->apMag);
+    return;
+}
+
+pmGrowthCurve *pmGrowthCurveAlloc (psF32 minRadius, psF32 maxRadius, psF32 dRadius)
+{
+
+    pmGrowthCurve *growth = psAlloc (sizeof(pmGrowthCurve));
+    psMemSetDeallocator(growth, (psFreeFunc) pmGrowthCurveFree);
+
+    growth->radius = psVectorCreate (NULL, minRadius, maxRadius, dRadius, PS_TYPE_F32);
+    growth->apMag  = psVectorAlloc (growth->radius->n, PS_TYPE_F32);
+
+    // XXX may want to extend this to allow for a different refRadius;
+    growth->refRadius = maxRadius;
+    growth->maxRadius = maxRadius;
+    growth->apLoss = 0.0;
+    growth->fitMag = 0.0;
+    return growth;
+}
+
+psF32 pmGrowthCurveCorrect (pmGrowthCurve *growth, psF32 radius)
+{
+
+    float apRad = psVectorInterpolate (growth->radius, growth->apMag, radius);
+    float apCor = growth->apRef - apRad;
+    return apCor;
+}
+
+// return the last entry below or first entry above key value
+int psVectorBracket (psVector *index, psF32 key, bool above)
+{
+
+    int N;
+    int Nlo = 0;
+    int Nhi = index->n;
+
+    if (above) {
+        while (Nhi - Nlo > 10) {
+            N = 0.5*(Nlo + Nhi);
+            if (index->data.F32[N] > key) {
+                Nhi = N;
+            } else {
+                Nlo = N - 1;
+            }
+        }
+        // at this point, index[Nhi] > key >= index[Nlo]
+        N = Nlo;
+        while ((index->data.F32[N] <= key) && (N < Nhi)) {
+            N++;
+        }
+        return (N);
+    }
+    while (Nhi - Nlo > 10) {
+        N = 0.5*(Nlo + Nhi);
+        if (index->data.F32[N] < key) {
+            Nlo = N;
+        } else {
+            Nhi = N + 1;
+        }
+    }
+    // at this point, index[Nhi] >= key > index[Nlo]
+    N = Nhi;
+    while ((index->data.F32[N] >= key) && (N > Nlo)) {
+        N--;
+    }
+    return (N);
+}
+
+// search for the bins bounding key in index, interpolate the corresponding values
+psF32 psVectorInterpolate (psVector *index, psVector *value, psF32 key)
+{
+
+    int n0 = 0;
+    int n1 = 0;
+
+    // extrapolate at ends
+    if (key < index->data.F32[0]) {
+        n0 = 0;
+        n1 = 1;
+    }
+
+    // extrapolate at ends
+    if (key > index->data.F32[index->n-1]) {
+        n0 = index->n-2;
+        n1 = index->n-1;
+    }
+
+    if (n1 == 0) {
+        int n0 = psVectorBracket (index, key, FALSE);
+        n1 = n0 + 1;
+    }
+
+    if (n0 == index->n-1) {
+        n1 = n0;
+        n0 = n1 - 1;
+    }
+
+    float dy = value->data.F32[n1] - value->data.F32[n0];
+    float dx = index->data.F32[n1] - index->data.F32[n0];
+    float dX = key - index->data.F32[n0];
+    float dY = dX * (dy/dx);
+    float result = value->data.F32[n0] + dY;
+    return result;
+}
+
Index: /branches/rel10_ifa/psModules/src/objects/pmGrowthCurve.h
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmGrowthCurve.h	(revision 6448)
+++ /branches/rel10_ifa/psModules/src/objects/pmGrowthCurve.h	(revision 6448)
@@ -0,0 +1,21 @@
+# ifndef PM_GROWTH_CURVE_H
+# define PM_GROWTH_CURVE_H
+
+typedef struct
+{
+    psVector *radius;
+    psVector *apMag;
+    psF32 refRadius;
+    psF32 maxRadius;
+    psF32 fitMag;
+    psF32 apRef;   // apMag[refRadius]
+    psF32 apLoss;  // fitMag - apRef
+}
+pmGrowthCurve;
+
+pmGrowthCurve *pmGrowthCurveAlloc (psF32 minRadius, psF32 maxRadius, psF32 dRadius);
+int psVectorBracket (psVector *index, psF32 key, bool above);
+psF32 psVectorInterpolate (psVector *index, psVector *value, psF32 key);
+psF32 pmGrowthCurveCorrect (pmGrowthCurve *growth, psF32 radius);
+
+# endif /* PM_GROWTH_CURVE_H */
Index: /branches/rel10_ifa/psModules/src/objects/pmModelGroup.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmModelGroup.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmModelGroup.c	(revision 6448)
@@ -25,5 +25,4 @@
     if (modelGroup == NULL)
         return;
-    psFree (modelGroup);
     return;
 }
@@ -60,4 +59,11 @@
     }
     Nmodels = Nnew;
+    return;
+}
+
+void pmModelGroupCleanup (void)
+{
+
+    psFree (models);
     return;
 }
Index: /branches/rel10_ifa/psModules/src/objects/pmModelGroup.h
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmModelGroup.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmModelGroup.h	(revision 6448)
@@ -9,6 +9,6 @@
  *  @author EAM, IfA
  *
- *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-12-24 01:24:32 $
+ *  @version $Revision: 1.2.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -92,8 +92,8 @@
  * 
  *  This function constructs the PSF model for the given source based on the
- *  supplied psf and the FLT model for the object.
- * 
- */
-typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+ *  supplied psf and the EXT model for the object.
+ * 
+ */
+typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelEXT, pmPSF *psf);
 
 /**
@@ -215,4 +215,7 @@
 void pmModelGroupInit (void);
 
+// free the internal (static) model group
+void pmModelGroupCleanup (void);
+
 // add a new model to the internal (static) model group
 void pmModelGroupAdd (pmModelGroup *model);
Index: /branches/rel10_ifa/psModules/src/objects/pmObjects.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmObjects.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmObjects.c	(revision 6448)
@@ -6,6 +6,6 @@
  *  @author EAM, IfA: significant modifications.
  *
- *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2006-02-06 22:17:54 $
+ *  @version $Revision: 1.9.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -25,5 +25,5 @@
                     psS32 y,
                     psF32 counts,
-                    pmPeakType class)
+                    pmPeakType type)
 {
     psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
@@ -32,5 +32,5 @@
     tmp->y = y;
     tmp->counts = counts;
-    tmp->class = class;
+    tmp->type = type;
 
     psTrace(__func__, 3, "---- %s() end ----\n", __func__);
@@ -77,5 +77,6 @@
     psFree(tmp->moments);
     psFree(tmp->modelPSF);
-    psFree(tmp->modelFLT);
+    psFree(tmp->modelEXT);
+    psFree(tmp->blends);
     psTrace(__func__, 4, "---- %s() end ----\n", __func__);
 }
@@ -85,5 +86,5 @@
 psVector containing the specified row of data from the psImage.
  
-XXX: Is there a better way to do this?
+XXX: Is there a better way to do this?  
 XXX EAM: does this really need to alloc a new vector???
 *****************************************************************************/
@@ -211,5 +212,5 @@
     // XXX EAM : i changed this to match pmModelEval above, but see
     // XXX EAM   the note below in pmSourceContour
-    psF32 oldValue = pmModelEval(source->modelFLT, source->pixels, subCol, subRow);
+    psF32 oldValue = pmModelEval(source->modelEXT, source->pixels, subCol, subRow);
     if (oldValue == level) {
         psTrace(__func__, 4, "---- %s() end ----\n", __func__);
@@ -233,5 +234,5 @@
 
     while (subCol != lastColumn) {
-        psF32 newValue = pmModelEval(source->modelFLT, source->pixels, subCol, subRow);
+        psF32 newValue = pmModelEval(source->modelEXT, source->pixels, subCol, subRow);
         if (oldValue == level) {
             psTrace(__func__, 4, "---- %s() end ----\n", __func__);
@@ -310,7 +311,9 @@
     tmp->mask = NULL;
     tmp->moments = NULL;
+    tmp->blends = NULL;
     tmp->modelPSF = NULL;
-    tmp->modelFLT = NULL;
-    tmp->type = 0;
+    tmp->modelEXT = NULL;
+    tmp->type = PM_SOURCE_UNKNOWN;
+    tmp->mode = PM_SOURCE_DEFAULT;
     psMemSetDeallocator(tmp, (psFreeFunc) sourceFree);
 
@@ -436,7 +439,9 @@
 XXX: In the output psArray elements, should we use the image row/column offsets?
      Currently, we do not.
+XXX EAM : this function needs to return peaks in *parent* coords
  
 XXX: Merge with CVS 1.20.  This had the proper code for images with a single
 row or column.
+ 
 *****************************************************************************/
 psArray *pmFindImagePeaks(const psImage *image,
@@ -456,4 +461,7 @@
     psArray *list = NULL;
 
+    psU32 col0 = image->col0;
+    psU32 row0 = image->row0;
+
     //
     // Find peaks in row 0 only.
@@ -462,4 +470,5 @@
     tmpRow = getRowVectorFromImage((psImage *) image, row);
     psVector *row1 = pmFindVectorPeaks(tmpRow, threshold);
+    // pmFindVectorPeaks returns coords in the vector, not corrected for col0
 
     for (psU32 i = 0 ; i < row1->n ; i++ ) {
@@ -474,5 +483,5 @@
 
                 if (image->data.F32[row][col] > threshold) {
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
                 }
             }
@@ -484,5 +493,5 @@
                     (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
                 if (image->data.F32[row][col] > threshold) {
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
                 }
             }
@@ -493,5 +502,5 @@
                     (image->data.F32[row][col] >= image->data.F32[row+1][col-1])) {
                 if (image->data.F32[row][col] > threshold) {
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
                 }
             }
@@ -532,5 +541,5 @@
                         (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
                     myType = PM_PEAK_EDGE;
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], myType);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], myType);
                 }
             } else if (col < (image->numCols - 1)) {
@@ -567,5 +576,5 @@
                         }
 
-                        list = myListAddPeak(list, row, col, image->data.F32[row][col], myType);
+                        list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], myType);
                     }
                 }
@@ -579,5 +588,5 @@
                         (image->data.F32[row][col] >= image->data.F32[row+1][col])) {
                     myType = PM_PEAK_EDGE;
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], myType);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], myType);
                 }
             } else {
@@ -603,5 +612,5 @@
                     (image->data.F32[row][col] >  image->data.F32[row][col+1])) {
                 if (image->data.F32[row][col] > threshold) {
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
                 }
             }
@@ -613,5 +622,5 @@
                     (image->data.F32[row][col] >= image->data.F32[row][col+1])) {
                 if (image->data.F32[row][col] > threshold) {
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
                 }
             }
@@ -622,5 +631,5 @@
                     (image->data.F32[row][col] >  image->data.F32[row][col-1])) {
                 if (image->data.F32[row][col] > threshold) {
-                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
                 }
             }
@@ -728,4 +737,5 @@
 XXX: Sync with IfA on whether the peak x/y coords are data structure coords,
      or they use the image row/column offsets.
+XXX  EAM : peak->x,y uses parent coordinates
  
 XXX: Should we simply set pmSource->peak = peak?  If so, should we increase
@@ -776,6 +786,51 @@
         return(false);
     }
-    source->moments = pmMomentsAlloc();
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
     source->moments->Sky = (psF32) tmpF64;
+    psTrace(__func__, 3, "---- %s(true) end ----\n", __func__);
+    return (true);
+}
+
+// A complementary function to pmSourceLocalSky: calculate the local median variance
+bool pmSourceLocalSkyVariance(
+    pmSource *source,
+    psStatsOptions statsOptions,
+    psF32 Radius)
+{
+    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->weight, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_INT_POSITIVE(Radius, false);
+    PS_ASSERT_INT_NONNEGATIVE(Radius, false);
+
+    psImage *image = source->weight;
+    psImage *mask  = source->mask;
+    pmPeak *peak  = source->peak;
+    psRegion srcRegion;
+
+    srcRegion = psRegionForSquare(peak->x, peak->y, Radius);
+    srcRegion = psRegionForImage(mask, srcRegion);
+
+    psImageMaskRegion(mask, srcRegion, "OR", PSPHOT_MASK_MARKED);
+    psStats *myStats = psStatsAlloc(statsOptions);
+    myStats = psImageStats(myStats, image, mask, 0xff);
+    psImageMaskRegion(mask, srcRegion, "AND", ~PSPHOT_MASK_MARKED);
+
+    psF64 tmpF64;
+    p_psGetStatValue(myStats, &tmpF64);
+    psFree(myStats);
+
+    if (isnan(tmpF64)) {
+        psTrace(__func__, 3, "---- %s(false) end ----\n", __func__);
+        return(false);
+    }
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
+    source->moments->dSky = (psF32) tmpF64;
     psTrace(__func__, 3, "---- %s(true) end ----\n", __func__);
     return (true);
@@ -830,4 +885,5 @@
     psS32 numPixels = 0;
     psF32 Sum = 0.0;
+    psF32 Var = 0.0;
     psF32 X1 = 0.0;
     psF32 Y1 = 0.0;
@@ -841,4 +897,6 @@
     psF32 xPeak = source->peak->x;
     psF32 yPeak = source->peak->y;
+    psF32 xOff = source->pixels->col0 - source->peak->x;
+    psF32 yOff = source->pixels->row0 - source->peak->y;
 
     // XXX why do I get different results for these two methods of finding Sx?
@@ -851,12 +909,19 @@
     // XXX EAM : mask == 0 is valid
 
+    psF32 **vPix = source->pixels->data.F32;
+    psF32 **vWgt = source->weight->data.F32;
+    psU8  **vMsk = source->mask->data.U8;
+
     for (psS32 row = 0; row < source->pixels->numRows ; row++) {
         for (psS32 col = 0; col < source->pixels->numCols ; col++) {
-            if ((source->mask != NULL) && (source->mask->data.U8[row][col])) {
+            if ((source->mask != NULL) && (vMsk[row][col])) {
                 continue;
             }
 
-            psF32 xDiff = col + source->pixels->col0 - xPeak;
-            psF32 yDiff = row + source->pixels->row0 - yPeak;
+            // psF32 xDiff = col + source->pixels->col0 - xPeak;
+            // psF32 yDiff = row + source->pixels->row0 - yPeak;
+
+            psF32 xDiff = col + xOff;
+            psF32 yDiff = row + yOff;
 
             // XXX EAM : calculate xDiff, yDiff up front;
@@ -866,27 +931,35 @@
             }
 
-            psF32 pDiff = source->pixels->data.F32[row][col] - sky;
+            psF32 pDiff = vPix[row][col] - sky;
+            psF32 wDiff = vWgt[row][col];
 
             // XXX EAM : check for valid S/N in pixel
             // XXX EAM : should this limit be user-defined?
-            if (pDiff / sqrt(source->weight->data.F32[row][col]) < 1) {
+            if (PS_SQR(pDiff) < wDiff) {
                 continue;
             }
 
+            Var += wDiff;
             Sum += pDiff;
-            X1  += xDiff * pDiff;
-            Y1  += yDiff * pDiff;
-            XY  += xDiff * yDiff * pDiff;
-
-            X2  += PS_SQR(xDiff) * pDiff;
-            Y2  += PS_SQR(yDiff) * pDiff;
-
-            peakPixel = PS_MAX (source->pixels->data.F32[row][col], peakPixel);
+
+            psF32 xWght = xDiff * pDiff;
+            psF32 yWght = yDiff * pDiff;
+
+            X1  += xWght;
+            Y1  += yWght;
+            XY  += xDiff * yWght;
+
+            X2  += xDiff * xWght;
+            Y2  += yDiff * yWght;
+
+            peakPixel = PS_MAX (vPix[row][col], peakPixel);
             numPixels++;
         }
     }
+
+    // if we have less than (1/4) of the possible pixels, force a retry
     // XXX EAM - the limit is a bit arbitrary.  make it user defined?
-    if ((numPixels < 3) || (Sum <= 0)) {
-        psTrace (".psModules.pmSourceMoments", 5, "no valid pixels for source\n");
+    if ((numPixels < 0.75*R2) || (Sum <= 0)) {
+        psTrace (".psModules.pmSourceMoments", 3, "no valid pixels for source\n");
         psTrace(__func__, 3, "---- %s(false) end ----\n", __func__);
         return (false);
@@ -905,5 +978,5 @@
     y = Y1/Sum;
     if ((fabs(x) > radius) || (fabs(y) > radius)) {
-        psTrace (".psModules.pmSourceMoments", 5,
+        psTrace (".psModules.pmSourceMoments", 3,
                  "large centroid swing; invalid peak %d, %d\n",
                  source->peak->x, source->peak->y);
@@ -918,4 +991,5 @@
     source->moments->Sxy = XY/Sum - x*y;
     source->moments->Sum = Sum;
+    source->moments->SN  = Sum / sqrt(Var);
     source->moments->Peak = peakPixel;
     source->moments->nPixels = numPixels;
@@ -976,6 +1050,6 @@
 
 /******************************************************************************
-pmSourcePSFClump(source, metadata): Find the likely PSF clump in the
-sigma-x, sigma-y plane. return 0,0 clump in case of error.
+    pmSourcePSFClump(source, metadata): Find the likely PSF clump in the 
+    sigma-x, sigma-y plane. return 0,0 clump in case of error. 
 *****************************************************************************/
 
@@ -1138,12 +1212,12 @@
 
 /******************************************************************************
-pmSourceRoughClass(source, metadata): make a guess at the source
-classification.
- 
-XXX: push the clump info into the metadata?
- 
-XXX: How can this function ever return FALSE?
- 
-XXX EAM : add the saturated mask value to metadata
+    pmSourceRoughClass(source, metadata): make a guess at the source
+    classification.
+     
+    XXX: push the clump info into the metadata?
+     
+    XXX: How can this function ever return FALSE?
+     
+    EAM: I moved S/N calculation to pmSourceMoments, using weight image
 *****************************************************************************/
 
@@ -1155,11 +1229,13 @@
 
     int Nsat     = 0;
-    int Ngal     = 0;
+    int Next     = 0;
     int Nstar    = 0;
     int Npsf     = 0;
     int Ncr      = 0;
     int Nsatstar = 0;
-    psRegion allArray = psRegionSet (0, 0, 0, 0);
-
+    // psRegion allArray = psRegionSet (0, 0, 0, 0);
+    psRegion inner;
+
+    // report stats on S/N values for star-like objects
     psVector *starsn = psVectorAlloc (sources->n, PS_TYPE_F32);
     starsn->n = 0;
@@ -1167,8 +1243,5 @@
     // check return status value (do these exist?)
     bool status;
-    psF32 RDNOISE    = psMetadataLookupF32 (&status, metadata, "RDNOISE");
-    psF32 GAIN       = psMetadataLookupF32 (&status, metadata, "GAIN");
     psF32 PSF_SN_LIM = psMetadataLookupF32 (&status, metadata, "PSF_SN_LIM");
-    // psF32 SATURATE = psMetadataLookupF32 (&status, metadata, "SATURATE");
 
     // XXX allow clump size to be scaled relative to sigmas?
@@ -1178,25 +1251,21 @@
         pmSource *tmpSrc = (pmSource *) sources->data[i];
 
-        tmpSrc->peak->class = 0;
+        tmpSrc->peak->type = 0;
 
         psF32 sigX = tmpSrc->moments->Sx;
         psF32 sigY = tmpSrc->moments->Sy;
 
-        // calculate and save signal-to-noise estimates
-        psF32 S  = tmpSrc->moments->Sum;
-        psF32 A  = 4 * M_PI * sigX * sigY;
-        psF32 B  = tmpSrc->moments->Sky;
-        psF32 RT = sqrt(S + (A * B) + (A * PS_SQR(RDNOISE) / sqrt(GAIN)));
-        psF32 SN = (S * sqrt(GAIN) / RT);
-        tmpSrc->moments->SN = SN;
-
         // XXX EAM : can we use the value of SATURATE if mask is NULL?
-        int Nsatpix = psImageCountPixelMask (tmpSrc->mask, allArray, PSPHOT_MASK_SATURATED);
+        // inner = psRegionForSquare (tmpSrc->peak->x - tmpSrc->mask->col0, tmpSrc->peak->y - tmpSrc->mask->row0, 2);
+        inner = psRegionForSquare (tmpSrc->peak->x, tmpSrc->peak->y, 2);
+        int Nsatpix = psImageCountPixelMask (tmpSrc->mask, inner, PSPHOT_MASK_SATURATED);
 
         // saturated star (size consistent with PSF or larger)
         // Nsigma should be user-configured parameter
         bool big = (sigX > (clump.X - clump.dX)) && (sigY > (clump.Y - clump.dY));
+        big = true;
         if ((Nsatpix > 1) && big) {
-            tmpSrc->type = PM_SOURCE_SATSTAR;
+            tmpSrc->type = PM_SOURCE_STAR;
+            tmpSrc->mode = PM_SOURCE_SATSTAR;
             Nsatstar ++;
             continue;
@@ -1206,4 +1275,5 @@
         if (Nsatpix > 1) {
             tmpSrc->type = PM_SOURCE_SATURATED;
+            tmpSrc->mode = PM_SOURCE_DEFAULT;
             Nsat ++;
             continue;
@@ -1215,24 +1285,27 @@
         if ((sigX < 0.05) || (sigY < 0.05)) {
             tmpSrc->type = PM_SOURCE_DEFECT;
+            tmpSrc->mode = PM_SOURCE_DEFAULT;
             Ncr ++;
             continue;
         }
 
-        // likely unsaturated galaxy (too large to be stellar)
+        // likely unsaturated extended source (too large to be stellar)
         if ((sigX > (clump.X + 3*clump.dX)) || (sigY > (clump.Y + 3*clump.dY))) {
-            tmpSrc->type = PM_SOURCE_GALAXY;
-            Ngal ++;
+            tmpSrc->type = PM_SOURCE_EXTENDED;
+            tmpSrc->mode = PM_SOURCE_DEFAULT;
+            Next ++;
             continue;
         }
 
         // the rest are probable stellar objects
-        starsn->data.F32[starsn->n] = SN;
+        starsn->data.F32[starsn->n] = tmpSrc->moments->SN;
         starsn->n ++;
         Nstar ++;
 
-        // PSF star (within 1.5 sigma of clump center
+        // PSF star (within 1.5 sigma of clump center, S/N > limit)
         psF32 radius = hypot ((sigX-clump.X)/clump.dX, (sigY-clump.Y)/clump.dY);
-        if ((SN > PSF_SN_LIM) && (radius < 1.5)) {
-            tmpSrc->type = PM_SOURCE_PSFSTAR;
+        if ((tmpSrc->moments->SN > PSF_SN_LIM) && (radius < 1.5)) {
+            tmpSrc->type = PM_SOURCE_STAR;
+            tmpSrc->mode = PM_SOURCE_PSFSTAR;
             Npsf ++;
             continue;
@@ -1240,5 +1313,6 @@
 
         // random type of star
-        tmpSrc->type = PM_SOURCE_OTHER;
+        tmpSrc->type = PM_SOURCE_STAR;
+        tmpSrc->mode = PM_SOURCE_DEFAULT;
     }
 
@@ -1254,5 +1328,5 @@
     psTrace (".pmObjects.pmSourceRoughClass", 2, "Nstar:    %3d\n", Nstar);
     psTrace (".pmObjects.pmSourceRoughClass", 2, "Npsf:     %3d\n", Npsf);
-    psTrace (".pmObjects.pmSourceRoughClass", 2, "Ngal:     %3d\n", Ngal);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Next:     %3d\n", Next);
     psTrace (".pmObjects.pmSourceRoughClass", 2, "Nsatstar: %3d\n", Nsatstar);
     psTrace (".pmObjects.pmSourceRoughClass", 2, "Nsat:     %3d\n", Nsat);
@@ -1264,5 +1338,5 @@
 
 /** pmSourceDefinePixels()
- *
+ * 
  * Define psImage subarrays for the source located at coordinates x,y on the
  * image set defined by readout. The pixels defined by this operation consist of
@@ -1276,7 +1350,7 @@
  * example). This function should be used to define a region of interest around a
  * source, including both source and sky pixels.
- *
+ * 
  * XXX: must code this.
- *
+ * 
  */
 bool pmSourceDefinePixels(
@@ -1294,7 +1368,7 @@
 
 /******************************************************************************
-pmSourceSetPixelsCircle(source, image, radius)
- 
-XXX: This was replaced by DefinePixels in SDRS.  Remove it.
+    pmSourceSetPixelsCircle(source, image, radius)
+     
+    XXX: This was replaced by DefinePixels in SDRS.  Remove it.
 *****************************************************************************/
 bool pmSourceSetPixelsCircle(pmSource *source,
@@ -1369,12 +1443,12 @@
 
 /******************************************************************************
-pmSourceModelGuess(source, model): This function allocates a new
-pmModel structure based on the given modelType specified in the argument list.
-The corresponding pmModelGuess function is returned, and used to
-supply the values of the params array in the pmModel structure.
- 
-XXX: Many parameters are based on the src->moments structure, which is in
-image, not subImage coords.  Therefore, the calls to the model evaluation
-functions will be in image, not subImage coords.  Remember this.
+    pmSourceModelGuess(source, model): This function allocates a new
+    pmModel structure based on the given modelType specified in the argument list.  
+    The corresponding pmModelGuess function is returned, and used to 
+    supply the values of the params array in the pmModel structure.  
+     
+    XXX: Many parameters are based on the src->moments structure, which is in
+    image, not subImage coords.  Therefore, the calls to the model evaluation
+    functions will be in image, not subImage coords.  Remember this.
 *****************************************************************************/
 pmModel *pmSourceModelGuess(pmSource *source,
@@ -1394,23 +1468,23 @@
 
 /******************************************************************************
-evalModel(source, level, row): a private function which evaluates the
-source->modelPSF function at the specified coords.  The coords are subImage, not
-image coords.
- 
-NOTE: The coords are in subImage source->pixel coords, not image coords.
- 
-XXX: reverse order of row,col args?
- 
-XXX: rename all coords in this file such that their name defines whether
-the coords is in subImage or image space.
- 
-XXX: This should probably be a public pmModules function.
- 
-XXX: Use static vectors for x.
- 
-XXX: Figure out if it's (row, col) or (col, row) for the model functions.
- 
-XXX: For a while, the first psVectorAlloc() was generating a seg fault during
-testing.  Try to reproduce that and debug.
+    evalModel(source, level, row): a private function which evaluates the
+    source->modelPSF function at the specified coords.  The coords are subImage, not
+    image coords.
+     
+    NOTE: The coords are in subImage source->pixel coords, not image coords.
+     
+    XXX: reverse order of row,col args?
+     
+    XXX: rename all coords in this file such that their name defines whether
+    the coords is in subImage or image space.
+     
+    XXX: This should probably be a public pmModules function.
+     
+    XXX: Use static vectors for x.
+     
+    XXX: Figure out if it's (row, col) or (col, row) for the model functions.
+     
+    XXX: For a while, the first psVectorAlloc() was generating a seg fault during
+    testing.  Try to reproduce that and debug.
 *****************************************************************************/
 
@@ -1441,15 +1515,15 @@
 
 /******************************************************************************
-pmSourceContour(src, img, level, mode): For an input subImage, and model, this
-routine returns a psArray of coordinates that evaluate to the specified level.
- 
-XXX: Probably should remove the "image" argument.
-XXX: What type should the output coordinate vectors consist of?  col,row?
-XXX: Why a pmArray output?
-XXX: doex x,y correspond with col,row or row/col?
-XXX: What is mode?
-XXX: The top, bottom of the contour is not correctly determined.
-XXX EAM : this function is using the model for the contour, but it should
-          be using only the image counts
+    pmSourceContour(src, img, level, mode): For an input subImage, and model, this
+    routine returns a psArray of coordinates that evaluate to the specified level.
+     
+    XXX: Probably should remove the "image" argument.
+    XXX: What type should the output coordinate vectors consist of?  col,row?
+    XXX: Why a pmArray output?
+    XXX: doex x,y correspond with col,row or row/col?
+    XXX: What is mode?
+    XXX: The top, bottom of the contour is not correctly determined.
+    XXX EAM : this function is using the model for the contour, but it should
+              be using only the image counts
 *****************************************************************************/
 psArray *pmSourceContour(pmSource *source,
@@ -1464,6 +1538,6 @@
     PS_ASSERT_PTR_NON_NULL(source->peak, false);
     PS_ASSERT_PTR_NON_NULL(source->pixels, false);
-    PS_ASSERT_PTR_NON_NULL(source->modelFLT, false);
-    // XXX EAM : what is the purpose of modelPSF/modelFLT?
+    PS_ASSERT_PTR_NON_NULL(source->modelEXT, false);
+    // XXX EAM : what is the purpose of modelPSF/modelEXT?
 
     //
@@ -1558,17 +1632,19 @@
 }
 
-// XXX EAM : these are better starting values, but should be available from metadata?
-#define PM_SOURCE_FIT_MODEL_NUM_ITERATIONS 15
-#define PM_SOURCE_FIT_MODEL_TOLERANCE 0.1
-/******************************************************************************
-pmSourceFitModel(source, model): must create the appropiate arguments to the
-LM minimization routines for the various p_pmMinLM_XXXXXX_Vec() functions.
- 
-XXX: should there be a mask value?
-XXX EAM : fit the specified model (not necessarily the one in source)
-*****************************************************************************/
-bool pmSourceFitModel_v5(pmSource *source,
-                         pmModel *model,
-                         const bool PSF)
+// save a static values so they may be set externally
+static psF32 PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = 15;
+static psF32 PM_SOURCE_FIT_MODEL_TOLERANCE = 0.1;
+
+bool pmSourceFitModelInit (float nIter, float tol)
+{
+
+    PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = nIter;
+    PM_SOURCE_FIT_MODEL_TOLERANCE = tol;
+    return true;
+}
+
+bool pmSourceFitModel (pmSource *source,
+                       pmModel *model,
+                       const bool PSF)
 {
     psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
@@ -1579,11 +1655,12 @@
     PS_ASSERT_PTR_NON_NULL(source->mask, false);
     PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    // XXX EAM : is it necessary for the mask & weight to exist?  the
+    //           tests below could be conditions (!NULL)
+
     psBool fitStatus = true;
     psBool onPic     = true;
     psBool rc        = true;
 
-    // XXX EAM : is it necessary for the mask & weight to exist?  the
-    //           tests below could be conditions (!NULL)
-
     psVector *params = model->params;
     psVector *dparams = model->dparams;
@@ -1592,51 +1669,57 @@
     pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
 
-    int nParams = PSF ? params->n - 4 : params->n;
-
-    // find the number of valid pixels
-    // XXX EAM : this loop and the loop below could just be one pass
-    //           using the psArrayAdd and psVectorExtend functions
-    psS32 count = 0;
+    int nParams = PSF ? 4 : params->n;
+
+    // maximum number of valid pixels
+    psS32 nPix = source->pixels->numRows * source->pixels->numCols;
+
+    // construct the coordinate and value entries
+    psArray *x = psArrayAlloc(nPix);
+    psVector *y = psVectorAlloc(nPix, PS_TYPE_F32);
+    psVector *yErr = psVectorAlloc(nPix, PS_TYPE_F32);
+
+    nPix = 0;
     for (psS32 i = 0; i < source->pixels->numRows; i++) {
         for (psS32 j = 0; j < source->pixels->numCols; j++) {
-            if (source->mask->data.U8[i][j] == 0) {
-                count++;
-            }
-        }
-    }
-    if (count <  nParams + 1) {
+            // skip masked points
+            if (source->mask->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+
+            psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+            x->data[nPix] = (psPtr *) coord;
+            y->data.F32[nPix] = source->pixels->data.F32[i][j];
+            // psMinimizeLMChi2 takes wt = 1/dY^2
+            yErr->data.F32[nPix] = 1.0 / source->weight->data.F32[i][j];
+            nPix++;
+        }
+    }
+    if (nPix <  nParams + 1) {
         psTrace (".pmObjects.pmSourceFitModel", 4, "insufficient valid pixels\n");
         psTrace(__func__, 3, "---- %s(false) end ----\n", __func__);
         model->status = PM_MODEL_BADARGS;
+        psFree (x);
+        psFree (y);
+        psFree (yErr);
         return(false);
     }
-
-    // construct the coordinate and value entries
-    psArray *x = psArrayAlloc(count);
-    psVector *y = psVectorAlloc(count, PS_TYPE_F32);
-    psVector *yErr = psVectorAlloc(count, PS_TYPE_F32);
-    psS32 tmpCnt = 0;
-    for (psS32 i = 0; i < source->pixels->numRows; i++) {
-        for (psS32 j = 0; j < source->pixels->numCols; j++) {
-            if (source->mask->data.U8[i][j] == 0) {
-                psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
-                // XXX: Convert i/j to image space:
-                // XXX EAM: coord order is (x,y) == (col,row)
-                coord->data.F32[0] = (psF32) (j + source->pixels->col0);
-                coord->data.F32[1] = (psF32) (i + source->pixels->row0);
-                x->data[tmpCnt] = (psPtr *) coord;
-                y->data.F32[tmpCnt] = source->pixels->data.F32[i][j];
-                yErr->data.F32[tmpCnt] = sqrt (source->weight->data.F32[i][j]);
-                // XXX EAM : note the wasted effort: we carry dY^2, take sqrt(), then
-                //           the minimization function calculates sq()
-                tmpCnt++;
-            }
-        }
-    }
-
+    x->n = nPix;
+    y->n = nPix;
+    yErr->n = nPix;
+
+    // XXX EAM : the new minimization API supplies the constraints as a struct
     psMinimization *myMin = psMinimizationAlloc(PM_SOURCE_FIT_MODEL_NUM_ITERATIONS,
                             PM_SOURCE_FIT_MODEL_TOLERANCE);
-
-    // PSF model only fits first 4 parameters, FLT model fits all
+    psMinConstrain *constrain = psMinConstrainAlloc();
+
+    // PSF model only fits first 4 parameters, EXT model fits all
     if (PSF) {
         paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
@@ -1648,15 +1731,15 @@
         }
     }
-
-    // XXX EAM : covar must be F64?
+    constrain->paramMask = paramMask;
+
+    // Set the parameter range checks
+    pmModelLimits modelLimits = pmModelLimits_GetFunction (model->type);
+    modelLimits (&constrain->paramDelta, &constrain->paramMin, &constrain->paramMax);
+
     psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
 
     psTrace (".pmObjects.pmSourceFitModel", 5, "fitting function\n");
 
-    psMinConstrain *constrain = psMinConstrainAlloc();
-    constrain->paramMask = paramMask;
-    fitStatus = psMinimizeLMChi2(myMin, covar, params, constrain,
-                                 x, y, yErr, modelFunc);
-    psFree(constrain);
+    fitStatus = psMinimizeLMChi2(myMin, covar, params, constrain, x, y, yErr, modelFunc);
     for (int i = 0; i < dparams->n; i++) {
         if ((paramMask != NULL) && paramMask->data.U8[i])
@@ -1673,5 +1756,5 @@
     if (paramMask != NULL) {
         psVector *delta = psVectorAlloc (params->n, PS_TYPE_F64);
-        psMinimizeGaussNewtonDelta (delta, params, NULL, x, y, yErr, modelFunc);
+        psMinimizeGaussNewtonDelta(delta, params, NULL, x, y, yErr, modelFunc);
         for (int i = 0; i < dparams->n; i++) {
             if (!paramMask->data.U8[i])
@@ -1698,4 +1781,6 @@
     }
 
+    source->mode |= PM_SOURCE_FITTED;
+
     psFree(x);
     psFree(y);
@@ -1703,177 +1788,9 @@
     psFree(myMin);
     psFree(covar);
-    psFree(paramMask);
-
-    rc = (onPic && fitStatus);
-    psTrace(__func__, 3, "---- %s(%d) end ----\n", __func__, rc);
-    return(rc);
-}
-
-// XXX EAM : new version with parameter range limits and weight enhancement
-bool pmSourceFitModel (pmSource *source,
-                       pmModel *model,
-                       const bool PSF)
-{
-    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
-    PS_ASSERT_PTR_NON_NULL(source, false);
-    PS_ASSERT_PTR_NON_NULL(source->moments, false);
-    PS_ASSERT_PTR_NON_NULL(source->peak, false);
-    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
-    PS_ASSERT_PTR_NON_NULL(source->mask, false);
-    PS_ASSERT_PTR_NON_NULL(source->weight, false);
-
-    // XXX EAM : is it necessary for the mask & weight to exist?  the
-    //           tests below could be conditions (!NULL)
-
-    psBool fitStatus = true;
-    psBool onPic     = true;
-    psBool rc        = true;
-    psF32  Ro, ymodel;
-
-    psVector *params = model->params;
-    psVector *dparams = model->dparams;
-    psVector *paramMask = NULL;
-
-    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
-
-    // XXX EAM : I need to use the sky value to constrain the weight model
-    int nParams = PSF ? params->n - 4 : params->n;
-    psF32 So = params->data.F32[0];
-
-    // find the number of valid pixels
-    // XXX EAM : this loop and the loop below could just be one pass
-    //           using the psArrayAdd and psVectorExtend functions
-    psS32 count = 0;
-    for (psS32 i = 0; i < source->pixels->numRows; i++) {
-        for (psS32 j = 0; j < source->pixels->numCols; j++) {
-            if (source->mask->data.U8[i][j] == 0) {
-                count++;
-            }
-        }
-    }
-    if (count <  nParams + 1) {
-        psTrace (".pmObjects.pmSourceFitModel", 4, "insufficient valid pixels\n");
-        psTrace(__func__, 3, "---- %s(false) end ----\n", __func__);
-        model->status = PM_MODEL_BADARGS;
-        return(false);
-    }
-
-    // construct the coordinate and value entries
-    psArray *x = psArrayAlloc(count);
-    psVector *y = psVectorAlloc(count, PS_TYPE_F32);
-    psVector *yErr = psVectorAlloc(count, PS_TYPE_F32);
-    psS32 tmpCnt = 0;
-    for (psS32 i = 0; i < source->pixels->numRows; i++) {
-        for (psS32 j = 0; j < source->pixels->numCols; j++) {
-            if (source->mask->data.U8[i][j] == 0) {
-                psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
-                // XXX: Convert i/j to image space:
-                // XXX EAM: coord order is (x,y) == (col,row)
-                coord->data.F32[0] = (psF32) (j + source->pixels->col0);
-                coord->data.F32[1] = (psF32) (i + source->pixels->row0);
-                x->data[tmpCnt] = (psPtr *) coord;
-                y->data.F32[tmpCnt] = source->pixels->data.F32[i][j];
-
-                // compare observed flux to model flux to adjust weight
-                ymodel = modelFunc (NULL, model->params, coord);
-
-                // this test enhances the weight based on deviation from the model flux
-                Ro = 1.0 + fabs (y->data.F32[tmpCnt] - ymodel) / sqrt(PS_SQR(ymodel - So) + PS_SQR(So));
-
-                // psMinimizeLMChi2_EAM takes wt = 1/dY^2
-                if (source->weight->data.F32[i][j] == 0) {
-                    yErr->data.F32[tmpCnt] = 0.0;
-                } else {
-                    yErr->data.F32[tmpCnt] = 1.0 / (source->weight->data.F32[i][j] * Ro);
-                }
-                tmpCnt++;
-            }
-        }
-    }
-
-    psMinimization *myMin = psMinimizationAlloc(PM_SOURCE_FIT_MODEL_NUM_ITERATIONS,
-                            PM_SOURCE_FIT_MODEL_TOLERANCE);
-
-    // PSF model only fits first 4 parameters, FLT model fits all
-    if (PSF) {
-        paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
-        for (int i = 0; i < 4; i++) {
-            paramMask->data.U8[i] = 0;
-        }
-        for (int i = 4; i < paramMask->n; i++) {
-            paramMask->data.U8[i] = 1;
-        }
-    }
-
-    // XXX EAM : I've added three types of parameter range checks
-    // XXX EAM : this requires my new psMinimization functions
-    pmModelLimits modelLimits = pmModelLimits_GetFunction (model->type);
-    psVector *beta_lim = NULL;
-    psVector *params_min = NULL;
-    psVector *params_max = NULL;
-
-    // XXX EAM : in this implementation, I pass in the limits with the covar matrix.
-    //           in the SDRS, I define a new psMinimization which will take these in
-    psImage *covar = psImageAlloc (params->n, 3, PS_TYPE_F64);
-    modelLimits (&beta_lim, &params_min, &params_max);
-    for (int i = 0; i < params->n; i++) {
-        covar->data.F64[0][i] = beta_lim->data.F32[i];
-        covar->data.F64[1][i] = params_min->data.F32[i];
-        covar->data.F64[2][i] = params_max->data.F32[i];
-    }
-
-    psTrace (".pmObjects.pmSourceFitModel", 5, "fitting function\n");
-    psMinConstrain *constrain = psMinConstrainAlloc();
-    constrain->paramMask = paramMask;
-    fitStatus = psMinimizeLMChi2(myMin, covar, params, constrain,
-                                 x, y, yErr, modelFunc);
+    psFree(constrain->paramMask);
+    psFree(constrain->paramMin);
+    psFree(constrain->paramMax);
+    psFree(constrain->paramDelta);
     psFree(constrain);
-    for (int i = 0; i < dparams->n; i++) {
-        if ((paramMask != NULL) && paramMask->data.U8[i])
-            continue;
-        dparams->data.F32[i] = sqrt(covar->data.F64[i][i]);
-    }
-
-    // XXX EAM: we need to do something (give an error?) if rc is false
-    // XXX EAM: psMinimizeLMChi2 does not check convergence
-
-    // XXX EAM: save the resulting chisq, nDOF, nIter
-    model->chisq = myMin->value;
-    model->nIter = myMin->iter;
-    model->nDOF  = y->n - nParams;
-
-    // XXX EAM get the Gauss-Newton distance for fixed model parameters
-    if (paramMask != NULL) {
-        psVector *delta = psVectorAlloc (params->n, PS_TYPE_F64);
-        psMinimizeGaussNewtonDelta(delta, params, NULL, x, y, yErr, modelFunc);
-        for (int i = 0; i < dparams->n; i++) {
-            if (!paramMask->data.U8[i])
-                continue;
-            dparams->data.F32[i] = delta->data.F64[i];
-        }
-    }
-
-    // set the model success or failure status
-    if (!fitStatus) {
-        model->status = PM_MODEL_NONCONVERGE;
-    } else {
-        model->status = PM_MODEL_SUCCESS;
-    }
-
-    // XXX models can go insane: reject these
-    onPic &= (params->data.F32[2] >= source->pixels->col0);
-    onPic &= (params->data.F32[2] <  source->pixels->col0 + source->pixels->numCols);
-    onPic &= (params->data.F32[3] >= source->pixels->row0);
-    onPic &= (params->data.F32[3] <  source->pixels->row0 + source->pixels->numRows);
-    if (!onPic) {
-        model->status = PM_MODEL_OFFIMAGE;
-    }
-
-    psFree(x);
-    psFree(y);
-    psFree(yErr);
-    psFree(myMin);
-    psFree(covar);
-    psFree(paramMask);
 
     rc = (onPic && fitStatus);
Index: /branches/rel10_ifa/psModules/src/objects/pmObjects.h
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmObjects.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmObjects.h	(revision 6448)
@@ -10,6 +10,6 @@
  *  @author GLG, MHPCC
  *
- *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-12-24 01:24:32 $
+ *  @version $Revision: 1.5.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -27,5 +27,5 @@
 #include <math.h>
 #include "pslib.h"
-#include "pmAstrometry.h"
+#include "pmFPA.h"
 /**
  * In the object analysis process, we will use specific mask values to mark the
@@ -74,5 +74,5 @@
     int y;                              ///< Y-coordinate of peak pixel.
     float counts;                       ///< Value of peak pixel (above sky?).
-    pmPeakType class;                   ///< Description of peak.
+    pmPeakType type;                   ///< Description of peak.
 }
 pmPeak;
@@ -88,14 +88,15 @@
 typedef struct
 {
-    float x;    ///< X-coord of centroid.
-    float y;    ///< Y-coord of centroid.
+    float x;     ///< X-coord of centroid.
+    float y;     ///< Y-coord of centroid.
     float Sx;    ///< x-second moment.
     float Sy;    ///< y-second moment.
-    float Sxy;    ///< xy cross moment.
-    float Sum;    ///< Pixel sum above sky (background).
-    float Peak;    ///< Peak counts above sky.
-    float Sky;    ///< Sky level (background).
+    float Sxy;   ///< xy cross moment.
+    float Sum;   ///< Pixel sum above sky (background).
+    float Peak;  ///< Peak counts above sky.
+    float Sky;   ///< Sky level (background).
+    float dSky;  ///< local Sky variance
     float SN;    ///< approx signal-to-noise
-    int nPixels;   ///< Number of pixels used.
+    int nPixels; ///< Number of pixels used.
 }
 pmMoments;
@@ -131,9 +132,9 @@
 /** pmModel data structure
  *
- * Every source may have two types of models: a PSF model and a FLT (floating)
+ * Every source may have two types of models: a PSF model and a EXT (extended-source)
  * model. The PSF model represents the best fit of the image PSF to the specific
  * object. In this case, the PSF-dependent parameters are specified for the
- * object by the PSF, not by the fit. The FLT model represents the best fit of
- * the given model to the object, with all parameters floating in the fit.
+ * object by the PSF, not by the fit. The EXT model represents the best fit of
+ * the given model to the object, with all shape parameters floating in the fit.
  *
  */
@@ -146,5 +147,5 @@
     int nDOF;    ///< number of degrees of freedom
     int nIter;    ///< number of iterations to reach min
-    int status;         ///< fit status
+    pmModelStatus status;  ///< fit status
     float radius;   ///< fit radius actually used
 }
@@ -162,23 +163,26 @@
  */
 typedef enum {
+    PM_SOURCE_UNKNOWN,                  ///< a cosmic-ray
     PM_SOURCE_DEFECT,                   ///< a cosmic-ray
     PM_SOURCE_SATURATED,                ///< random saturated pixels
-
-    PM_SOURCE_SATSTAR,                  ///< a saturated star
-    PM_SOURCE_PSFSTAR,                  ///< a PSF star
-    PM_SOURCE_GOODSTAR,                 ///< a good-quality star
-
-    PM_SOURCE_POOR_FIT_PSF,             ///< poor quality PSF fit
-    PM_SOURCE_FAIL_FIT_PSF,             ///< failed to get a good PSF fit
-    PM_SOURCE_FAINTSTAR,                ///< below S/N cutoff
-
-    PM_SOURCE_GALAXY,                   ///< an extended object (galaxy)
-    PM_SOURCE_FAINT_GALAXY,             ///< a galaxy below S/N cutoff
-    PM_SOURCE_DROP_GALAXY,              ///< ?
-    PM_SOURCE_FAIL_FIT_GAL,             ///< failed on the galaxy fit
-    PM_SOURCE_POOR_FIT_GAL,             ///< poor quality galaxy fit
-
-    PM_SOURCE_OTHER,                    ///< unidentified
+    PM_SOURCE_STAR,                     ///< a good-quality star
+    PM_SOURCE_EXTENDED,                 ///< an extended object (eg, galaxy)
 } pmSourceType;
+
+typedef enum {
+    PM_SOURCE_DEFAULT    = 0x0000, ///<
+    PM_SOURCE_PSFMODEL   = 0x0001, ///<
+    PM_SOURCE_EXTMODEL   = 0x0002, ///<
+    PM_SOURCE_SUBTRACTED = 0x0004, ///<
+    PM_SOURCE_FITTED     = 0x0008, ///<
+    PM_SOURCE_FAIL       = 0x0010, ///<
+    PM_SOURCE_POOR       = 0x0020, ///<
+    PM_SOURCE_PAIR       = 0x0040, ///<
+    PM_SOURCE_PSFSTAR    = 0x0080, ///<
+    PM_SOURCE_SATSTAR    = 0x0100, ///<
+    PM_SOURCE_BLEND      = 0x0200, ///<
+    PM_SOURCE_LINEAR     = 0x0400, ///<
+    PM_SOURCE_TEMPSUB    = 0x0800, ///< XXX get me a better name!
+} pmSourceMode;
 
 /** pmSource data structure
@@ -197,8 +201,11 @@
     pmMoments *moments;   ///< Basic moments measure for the object.
     pmModel *modelPSF;   ///< PSF Model fit (parameters and type)
-    pmModel *modelFLT;   ///< FLT (floating) Model fit (parameters and type).
+    pmModel *modelEXT;   ///< EXT (floating) Model fit (parameters and type).
     pmSourceType type;   ///< Best identification of object.
+    pmSourceMode mode;   ///< Best identification of object.
+    psArray *blends;
     float apMag;
     float fitMag;
+    psRegion region; // area on image covered by selected pixels
 }
 pmSource;
@@ -213,5 +220,5 @@
     int y,    ///< Col-coordinate in image space
     float counts,   ///< The value of the peak pixel
-    pmPeakType class   ///< The type of peak pixel
+    pmPeakType type   ///< The type of peak pixel
 );
 
@@ -354,4 +361,11 @@
 
 
+// A complementary function to pmSourceLocalSky: calculate the local sky variance
+bool pmSourceLocalSkyVariance(
+    pmSource *source,   ///< The input image (float)
+    psStatsOptions statsOptions, ///< The statistic used in calculating the background sky
+    float Radius   ///< The inner radius of the square annulus to exclude
+);
+
 /** pmSourceMoments()
  *
@@ -469,4 +483,9 @@
 
 
+bool pmSourceFitModelInit(
+    float nIter,   ///< max number of allowed iterations
+    float tol      ///< convergence criterion
+);
+
 /** pmSourceFitModel()
  *
@@ -481,5 +500,5 @@
     pmSource *source,   ///< The input pmSource
     pmModel *model,   ///< model to be fitted
-    const bool PSF   ///< Treat model as PSF or FLT?
+    const bool PSF   ///< Treat model as PSF or EXT?
 );
 
@@ -563,4 +582,5 @@
  * This function converts the source classification into the closest available
  * approximation to the Dophot classification scheme:
+ * XXX EAM : fix this to use current source classification scheme
  *
  * PM_SOURCE_DEFECT: 8
@@ -610,5 +630,5 @@
     pmSource *source,   ///< The input pmSource
     pmModel *model,   ///< model to be fitted
-    const bool PSF   ///< Treat model as PSF or FLT?
+    const bool PSF   ///< Treat model as PSF or EXT?
 );
 
@@ -626,5 +646,5 @@
     pmSource *source,   ///< The input pmSource
     pmModel *model,   ///< model to be fitted
-    const bool PSF   ///< Treat model as PSF or FLT?
+    const bool PSF   ///< Treat model as PSF or EXT?
 );
 
Index: /branches/rel10_ifa/psModules/src/objects/pmPSF.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmPSF.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmPSF.c	(revision 6448)
@@ -6,6 +6,6 @@
  *  @author EAM, IfA
  *
- *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-12-24 01:24:32 $
+ *  @version $Revision: 1.4.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -57,4 +57,6 @@
         return;
 
+    psFree (psf->ApTrend);
+    psFree (psf->growth);
     psFree (psf->params);
     return;
@@ -69,6 +71,6 @@
  X-center
  Y-center
- ???: Sky background value?
- ???: Zo?
+ Sky background value
+ Object Normalization
  *****************************************************************************/
 pmPSF *pmPSFAlloc (pmModelType type)
@@ -83,4 +85,12 @@
     psf->dApResid = 0.0;
     psf->skyBias  = 0.0;
+    psf->skySat   = 0.0;
+
+    // the ApTrend components are (x, y, r2rflux, flux)
+    psf->ApTrend = psPolynomial4DAlloc (PS_POLYNOMIAL_ORD, 2, 2, 1, 1);
+    pmPSF_MaskApTrend (psf, PM_PSF_SKYBIAS);
+
+    // don't define a growth curve : user needs to choose radius bins
+    psf->growth = NULL;
 
     Nparams = pmModelParameterCount (type);
@@ -93,5 +103,5 @@
     for (int i = 0; i < psf->params->n; i++) {
         // XXX EAM : make this a user-defined value?
-        psf->params->data[i] = psPolynomial2DAlloc(1, 1, PS_POLYNOMIAL_ORD);
+        psf->params->data[i] = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 1, 1);
     }
 
@@ -169,11 +179,11 @@
 
 /*****************************************************************************
-pmModelFromPSF (*modelFLT, *psf):  use the model position parameters to
+pmModelFromPSF (*modelEXT, *psf):  use the model position parameters to
 construct a realization of the PSF model at the object coordinates
  *****************************************************************************/
-pmModel *pmModelFromPSF (pmModel *modelFLT, pmPSF *psf)
-{
-
-    // need to define the relationship between the modelFLT and modelPSF ?
+pmModel *pmModelFromPSF (pmModel *modelEXT, pmPSF *psf)
+{
+
+    // need to define the relationship between the modelEXT and modelPSF ?
 
     // find function used to set the model parameters
@@ -184,6 +194,172 @@
 
     // set model parameters for this source based on PSF information
-    modelFromPSFFunc (modelPSF, modelFLT, psf);
+    modelFromPSFFunc (modelPSF, modelEXT, psf);
 
     return (modelPSF);
 }
+
+// zero and mask out all terms:
+static bool maskAllTerms (psPolynomial4D *trend)
+{
+
+    for (int i = 0; i < trend->nX + 1; i++) {
+        for (int j = 0; j < trend->nY + 1; j++) {
+            for (int k = 0; k < trend->nZ + 1; k++) {
+                for (int m = 0; m < trend->nT + 1; m++) {
+                    trend->mask[i][j][k][m] = 1;  // mask coeff
+                    trend->coeff[i][j][k][m] = 0;  // zero coeff
+                }
+            }
+        }
+    }
+    return true;
+}
+
+/***********************************************
+ * this function masks the psf.ApTrend polynomial 
+ * to enable the specific subset of the coefficients
+ **********************************************/
+bool pmPSF_MaskApTrend (pmPSF *psf, pmPSF_ApTrendOptions option)
+{
+
+    switch (option) {
+    case PM_PSF_NONE:
+        maskAllTerms (psf->ApTrend);
+        return true;
+
+    case PM_PSF_CONSTANT:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        return true;
+
+    case PM_PSF_SKYBIAS:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_SKYSAT:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        psf->ApTrend->mask[0][0][0][1] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_XY_LIN:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        return true;
+
+    case PM_PSF_XY_QUAD:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[2][0][0][0] = 0;  // unmask x^2
+        psf->ApTrend->mask[1][1][0][0] = 0;  // unmask x y
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][2][0][0] = 0;  // unmask y^2
+        return true;
+
+    case PM_PSF_SKY_XY_LIN:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_SKYSAT_XY_LIN:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        psf->ApTrend->mask[0][0][0][1] = 0;  // unmask skysat
+        return true;
+
+    case PM_PSF_ALL:
+    default:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        psf->ApTrend->mask[0][0][0][1] = 0;  // unmask skysat
+
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[2][0][0][0] = 0;  // unmask x^2
+        psf->ApTrend->mask[1][1][0][0] = 0;  // unmask x y
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][2][0][0] = 0;  // unmask y^2
+        return true;
+    }
+    return false;
+}
+
+psMetadata *pmPSFtoMD (psMetadata *metadata, pmPSF *psf)
+{
+
+    if (metadata == NULL) {
+        metadata = psMetadataAlloc ();
+    }
+
+    char *modelName = pmModelGetType (psf->type);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_MODEL_NAME", PS_DATA_STRING, "PSF model name", modelName);
+
+    int nPar = pmModelParameterCount (psf->type)    ;
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_MODEL_NPAR", PS_DATA_S32, "PSF model parameter count", nPar);
+
+    for (int i = 0; i < nPar - 4; i++) {
+        psPolynomial2D *poly = psf->params->data[i];
+        psPolynomial2DtoMD (metadata, poly, "PSF_PAR%02d", i);
+    }
+    psPolynomial4DtoMD (metadata, psf->ApTrend, "APTREND");
+
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_AP_RESID", PS_DATA_F32, "aperture residual", psf->ApResid);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_dAP_RESID", PS_DATA_F32, "aperture residual scatter", psf->dApResid);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_SKY_BIAS", PS_DATA_F32, "sky bias level", psf->skyBias);
+
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_CHISQ", PS_DATA_F32, "chi-square for fit", psf->chisq);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_NSTARS", PS_DATA_S32, "number of stars used to measure PSF", psf->nPSFstars);
+
+    return metadata;
+}
+
+pmPSF *pmPSFfromMD (psMetadata *metadata)
+{
+
+    bool status;
+    char keyword[80];
+
+    char *modelName = psMetadataLookupPtr (&status, metadata, "PSF_MODEL_NAME");
+    pmModelType type = pmModelSetType (modelName);
+
+    pmPSF *psf = pmPSFAlloc (type);
+
+    int nPar = psMetadataLookupS32 (&status, metadata, "PSF_MODEL_NPAR");
+    if (nPar != pmModelParameterCount (psf->type))
+        psAbort ("read PSF" , "mismatch model par count");
+
+    for (int i = 0; i < nPar - 4; i++) {
+        sprintf (keyword, "PSF_PAR%02d", i);
+        psMetadata *folder = psMetadataLookupPtr (&status, metadata, keyword);
+        psPolynomial2D *poly = psPolynomial2DfromMD (folder);
+        psFree (psf->params->data[i]);
+        psf->params->data[i] = poly;
+    }
+    sprintf (keyword, "APTREND");
+    psMetadata *folder = psMetadataLookupPtr (&status, metadata, keyword);
+    psPolynomial4D *poly = psPolynomial4DfromMD (folder);
+    psFree (psf->ApTrend);
+    psf->ApTrend = poly;
+
+    psf->ApResid = psMetadataLookupF32 (&status, metadata, "PSF_AP_RESID");
+    psf->dApResid = psMetadataLookupF32 (&status, metadata, "PSF_dAP_RESID");
+    psf->skyBias = psMetadataLookupF32 (&status, metadata, "PSF_SKY_BIAS");
+
+    psf->chisq = psMetadataLookupF32 (&status, metadata, "PSF_CHISQ");
+    psf->nPSFstars = psMetadataLookupS32 (&status, metadata, "PSF_NSTARS");
+
+    psFree (metadata);
+    return (psf);
+}
Index: /branches/rel10_ifa/psModules/src/objects/pmPSF.h
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmPSF.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmPSF.h	(revision 6448)
@@ -6,6 +6,6 @@
  *  @author EAM, IfA
  *
- *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-10-10 19:53:40 $
+ *  @version $Revision: 1.1.22.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -16,4 +16,5 @@
 # define PM_PSF_H
 
+#include "pmGrowthCurve.h"
 
 /** pmPSF data structure
@@ -33,12 +34,27 @@
     pmModelType type;   ///< PSF Model in use
     psArray *params;   ///< Model parameters (psPolynomial2D)
+    psPolynomial4D *ApTrend;  ///< ApResid vs (x,y,rflux) (rflux = ten(0.4*mInst)
+    pmGrowthCurve *growth;  ///< apMag vs Radius
+    float ApResid;   ///< ???
+    float dApResid;   ///< ???
+    float skyBias;   ///< ???
+    float skySat;   ///< ???
     float chisq;   ///< PSF goodness statistic
-    float ApResid;                      ///< ???
-    float dApResid;                     ///< ???
-    float skyBias;                      ///< ???
     int nPSFstars;   ///< number of stars used to measure PSF
+    int nApResid;   ///< number of stars used to measure ApResid
 }
 pmPSF;
 
+typedef enum {
+    PM_PSF_NONE,
+    PM_PSF_CONSTANT,
+    PM_PSF_SKYBIAS,
+    PM_PSF_SKYSAT,
+    PM_PSF_XY_LIN,
+    PM_PSF_XY_QUAD,
+    PM_PSF_SKY_XY_LIN,
+    PM_PSF_SKYSAT_XY_LIN,
+    PM_PSF_ALL
+} pmPSF_ApTrendOptions;
 
 /**
@@ -85,3 +101,7 @@
 );
 
+bool pmPSF_MaskApTrend (pmPSF *psf, pmPSF_ApTrendOptions option);
+psMetadata *pmPSFtoMD (psMetadata *metadata, pmPSF *psf);
+pmPSF *pmPSFfromMD (psMetadata *metadata);
+
 # endif
Index: /branches/rel10_ifa/psModules/src/objects/pmPSFtry.c
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmPSFtry.c	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmPSFtry.c	(revision 6448)
@@ -5,6 +5,6 @@
  *  @author EAM, IfA
  *
- *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-12-24 01:24:32 $
+ *  @version $Revision: 1.4.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -33,5 +33,5 @@
     psFree (test->psf);
     psFree (test->sources);
-    psFree (test->modelFLT);
+    psFree (test->modelEXT);
     psFree (test->modelPSF);
     psFree (test->metric);
@@ -53,5 +53,5 @@
     test->psf      = pmPSFAlloc (type);
     test->sources  = psMemIncrRefCounter(sources);
-    test->modelFLT = psArrayAlloc (sources->n);
+    test->modelEXT = psArrayAlloc (sources->n);
     test->modelPSF = psArrayAlloc (sources->n);
     test->metric   = psVectorAlloc (sources->n, PS_TYPE_F64);
@@ -59,7 +59,7 @@
     test->mask     = psVectorAlloc (sources->n, PS_TYPE_U8);
 
-    for (int i = 0; i < test->modelFLT->n; i++) {
+    for (int i = 0; i < test->modelEXT->n; i++) {
         test->mask->data.U8[i]  = 0;
-        test->modelFLT->data[i] = NULL;
+        test->modelEXT->data[i] = NULL;
         test->modelPSF->data[i] = NULL;
         test->metric->data.F64[i] = 0;
@@ -87,5 +87,5 @@
     float x;
     float y;
-    int Nflt = 0;
+    int Next = 0;
     int Npsf = 0;
 
@@ -102,5 +102,6 @@
 
         // set temporary object mask and fit object
-        // fit model as FLT, not PSF
+        // fit model as EXT, not PSF
+
         psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PSPHOT_MASK_MARKED);
         status = pmSourceFitModel (source, model, false);
@@ -109,19 +110,16 @@
         // exclude the poor fits
         if (!status) {
-            psfTry->mask->data.U8[i] = PSFTRY_MASK_FLT_FAIL;
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_EXT_FAIL;
             psFree (model);
             continue;
         }
-        psfTry->modelFLT->data[i] = model;
-        Nflt ++;
-    }
-    psLogMsg ("psphot.psftry", 4, "fit flt:   %f sec for %d sources\n", psTimerMark ("fit"), sources->n);
-    psTrace ("psphot.psftry", 3, "keeping %d of %d PSF candidates (FLT)\n", Nflt, sources->n);
-
-    // make this optional?
-    // DumpModelFits (psfTry->modelFLT, "modelsFLT.dat");
+        psfTry->modelEXT->data[i] = model;
+        Next ++;
+    }
+    psLogMsg ("psphot.psftry", 4, "fit ext:   %f sec for %d sources\n", psTimerMark ("fit"), sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %d PSF candidates (EXT)\n", Next, sources->n);
 
     // stage 2: construct a psf (pmPSF) from this collection of model fits
-    pmPSFFromModels (psfTry->psf, psfTry->modelFLT, psfTry->mask);
+    pmPSFFromModels (psfTry->psf, psfTry->modelEXT, psfTry->mask);
 
     // stage 3: refit with fixed shape parameters
@@ -133,8 +131,8 @@
 
         pmSource *source = psfTry->sources->data[i];
-        pmModel  *modelFLT = psfTry->modelFLT->data[i];
+        pmModel  *modelEXT = psfTry->modelEXT->data[i];
 
         // set shape for this model based on PSF
-        pmModel *modelPSF = pmModelFromPSF (modelFLT, psfTry->psf);
+        pmModel *modelPSF = pmModelFromPSF (modelEXT, psfTry->psf);
         x = source->peak->x;
         y = source->peak->y;
@@ -169,4 +167,6 @@
 
     }
+    psfTry->psf->nPSFstars = Npsf;
+
     psLogMsg ("psphot.psftry", 4, "fit psf:   %f sec for %d sources\n", psTimerMark ("fit"), sources->n);
     psTrace ("psphot.psftry", 3, "keeping %d of %d PSF candidates (PSF)\n", Npsf, sources->n);
@@ -176,21 +176,13 @@
 
     // XXX this function wants aperture radius for pmSourcePhotometry
-    pmPSFtryMetric_Alt (psfTry, RADIUS);
-    psLogMsg ("psphot.pspsf", 3, "try model %s, ap-fit: %f +/- %f, sky bias: %f\n",
-              modelName,
-              psfTry->psf->ApResid,
-              psfTry->psf->dApResid,
-              psfTry->psf->skyBias);
+    pmPSFtryMetric (psfTry, RADIUS);
+    psLogMsg ("psphot.pspsf", 3, "try model %s, ap-fit: %f +/- %f : sky bias: %f\n",
+              modelName, psfTry->psf->ApResid, psfTry->psf->dApResid, psfTry->psf->skyBias);
 
     return (psfTry);
 }
 
-
 bool pmPSFtryMetric (pmPSFtry *psfTry, float RADIUS)
 {
-
-    float dBin;
-    int   nKeep, nSkip;
-
     // the measured (aperture - fit) magnitudes (dA == psfTry->metric)
     //   depend on both the true ap-fit (dAo) and the bias in the sky measurement:
@@ -202,192 +194,43 @@
     //   we use an outlier rejection to avoid this bias
 
-    // rflux = ten(0.4*fitMag);
-    psVector *rflux = psVectorAlloc (psfTry->sources->n, PS_TYPE_F64);
+    // r2rflux = radius^2 * ten(0.4*fitMag);
+    psVector *r2rflux = psVectorAlloc (psfTry->sources->n, PS_TYPE_F64);
     for (int i = 0; i < psfTry->sources->n; i++) {
         if (psfTry->mask->data.U8[i] & PSFTRY_MASK_ALL)
             continue;
-        rflux->data.F64[i] = pow(10.0, 0.4*psfTry->fitMag->data.F64[i]);
-    }
-
-    // find min and max of (1/flux):
-    psStats *stats = psStatsAlloc (PS_STAT_MIN | PS_STAT_MAX);
-    psVectorStats (stats, rflux, NULL, psfTry->mask, PSFTRY_MASK_ALL);
-
-    // build binned versions of rflux, metric
-    dBin = (stats->max - stats->min) / 10.0;
-    psVector *rfBin = psVectorCreate(NULL, stats->min, stats->max, dBin, PS_TYPE_F64);
-    psVector *daBin = psVectorAlloc (rfBin->n, PS_TYPE_F64);
-    psVector *maskB = psVectorAlloc (rfBin->n, PS_TYPE_U8);
-    psFree (stats);
-
-    psTrace ("psphot.metricmodel", 3, "rflux max: %g, min: %g, delta: %g\n", stats->max, stats->min, dBin);
-
-    // group data in daBin bins, measure lower 50% mean
-    for (int i = 0; i < daBin->n; i++) {
-
-        psVector *tmp = psVectorAlloc (psfTry->sources->n, PS_TYPE_F64);
-        tmp->n = 0;
-
-        // accumulate data within bin range
-        for (int j = 0; j < psfTry->sources->n; j++) {
-            // masked for: bad model fit, outlier in parameters
-            if (psfTry->mask->data.U8[j] & PSFTRY_MASK_ALL)
-                continue;
-
-            // skip points with extreme dA values
-            if (fabs(psfTry->metric->data.F64[j]) > 0.5)
-                continue;
-
-            // skip points outside of this bin
-            if (rflux->data.F64[j] < rfBin->data.F64[i] - 0.5*dBin)
-                continue;
-            if (rflux->data.F64[j] > rfBin->data.F64[i] + 0.5*dBin)
-                continue;
-
-            tmp->data.F64[tmp->n] = psfTry->metric->data.F64[j];
-            tmp->n ++;
-        }
-
-        // is this a valid point?
-        maskB->data.U8[i] = 0;
-        if (tmp->n < 2) {
-            maskB->data.U8[i] = 1;
-            psFree (tmp);
-            continue;
-        }
-
-        // dA values are contaminated with low outliers
-        // measure statistics only on upper 50% of points
-        // this would be easier if we could sort in reverse:
-        //
-        // psVectorSort (tmp, tmp);
-        // tmp->n = 0.5*tmp->n;
-        // stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
-        // psVectorStats (stats, tmp, NULL, NULL, 0);
-        // psTrace ("psphot.metricmodel", 4, "rfBin %d (%g): %d pts, %g\n", i, rfBin->data.F64[i], tmp->n, stats->sampleMedian);
-
-        psVectorSort (tmp, tmp);
-        nKeep = 0.5*tmp->n;
-        nSkip = tmp->n - nKeep;
-
-        psVector *tmp2 = psVectorAlloc (nKeep, PS_TYPE_F64);
-        for (int j = 0; j < tmp2->n; j++) {
-            tmp2->data.F64[j] = tmp->data.F64[j + nSkip];
-        }
-
-        stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
-        psVectorStats (stats, tmp2, NULL, NULL, 0);
-        psTrace ("psphot.metricmodel", 4, "rfBin %d (%g): %d pts, %g\n", i, rfBin->data.F64[i], tmp->n, stats->sampleMedian);
-
-        daBin->data.F64[i] = stats->sampleMedian;
-
-        psFree (stats);
-        psFree (tmp);
-        psFree (tmp2);
-    }
-
-    // linear fit to rfBin, daBin
-    psPolynomial1D *poly = psPolynomial1DAlloc(1, PS_POLYNOMIAL_ORD);
-    psStats *fitstat = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
-    poly = psVectorClipFitPolynomial1D (poly, fitstat, maskB, 1, daBin, NULL, rfBin);
-
-    // XXX EAM : this is the intended API (cycle 7? cycle 8?)
-    // poly = psVectorFitPolynomial1D(poly, maskB, 1, daBin, NULL, rfBin);
-
-    // XXX EAM : replace this when the above version is implemented
-    // poly = psVectorFitPolynomial1DOrd(poly, maskB, rfBin, daBin, NULL);
-
-    psVector *daBinFit = psPolynomial1DEvalVector (poly, rfBin);
-    psVector *daResid  = (psVector *) psBinaryOp (NULL, (void *) daBin, "-", (void *) daBinFit);
-
-    stats = psStatsAlloc (PS_STAT_CLIPPED_STDEV);
-    stats = psVectorStats (stats, daResid, NULL, maskB, 1);
-
-    psfTry->psf->ApResid = poly->coeff[0];
-    psfTry->psf->dApResid = stats->clippedStdev;
-    psfTry->psf->skyBias = poly->coeff[1] / (M_PI * PS_SQR(RADIUS));
-
-    psFree (rflux);
-    psFree (rfBin);
-    psFree (daBin);
-    psFree (maskB);
-    psFree (daBinFit);
-    psFree (daResid);
-    psFree (poly);
-    psFree (stats);
-    psFree (fitstat);
-
-    return true;
-}
-
-bool pmPSFtryMetric_Alt (pmPSFtry *try
-                         , float RADIUS)
-{
-
-    // the measured (aperture - fit) magnitudes (dA == try->metric)
-    //   depend on both the true ap-fit (dAo) and the bias in the sky measurement:
-    //     dA = dAo + dsky/flux
-    //   where flux is the flux of the star
-    // we fit this trend to find the infinite flux aperture correction (dAo),
-    //   the nominal sky bias (dsky), and the error on dAo
-    // the values of dA are contaminated by stars with close neighbors in the aperture
-    //   we use an outlier rejection to avoid this bias
-
-    // rflux = ten(0.4*fitMag);
-    psVector *rflux = psVectorAlloc (try
-                                     ->sources->n, PS_TYPE_F64);
-    for (int i = 0; i < try
-                ->sources->n; i++) {
-            if (try
-                    ->mask->data.U8[i] & PSFTRY_MASK_ALL) continue;
-            rflux->data.F64[i] = pow(10.0, 0.4*try
-                                     ->fitMag->data.F64[i]);
-        }
-
-    // XXX EAM : try 3hi/1lo sigma clipping on the rflux vs metric fit
+        r2rflux->data.F64[i] = PS_SQR(RADIUS) * pow(10.0, 0.4*psfTry->fitMag->data.F64[i]);
+    }
+
+    // use 3hi/1lo sigma clipping on the r2rflux vs metric fit
     psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
-
-    // XXX EAM
     stats->min = 1.0;
     stats->max = 3.0;
     stats->clipIter = 3;
 
-    // linear clipped fit to rfBin, daBin
-    psPolynomial1D *poly = psPolynomial1DAlloc (1, PS_POLYNOMIAL_ORD);
-    poly = psVectorClipFitPolynomial1D (poly, stats, try
-                                            ->mask, PSFTRY_MASK_ALL, try
-                                                ->metric, NULL, rflux);
-    fprintf (stderr, "fit stats: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
-
-    try
-        ->psf->ApResid = poly->coeff[0];
-    try
-        ->psf->dApResid = stats->sampleStdev;
-    try
-        ->psf->skyBias = poly->coeff[1] / (M_PI * PS_SQR(RADIUS));
-
-    fprintf (stderr, "*******************************************************************************\n");
-
-    FILE *f;
-    f = fopen ("apresid.dat", "w");
-    if (f == NULL)
-        psAbort ("pmPSFtry", "can't open output file");
-
-    for (int i = 0; i < try
-                ->sources->n; i++) {
-            fprintf (f, "%3d %8.4f %12.5e %8.4f %3d\n", i, try
-                         ->fitMag->data.F64[i], rflux->data.F64[i], try
-                             ->metric->data.F64[i], try
-                                 ->mask->data.U8[i]);
-        }
-    fclose (f);
-
-    psFree (rflux);
+    // fit ApTrend only to r2rflux, ignore x,y,flux variations for now
+    // linear clipped fit of ApResid to r2rflux
+    psPolynomial1D *poly = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
+    poly = psVectorClipFitPolynomial1D (poly, stats, psfTry->mask, PSFTRY_MASK_ALL, psfTry->metric, NULL, r2rflux);
+    psLogMsg ("pmPSFtryMetric", 4, "fit stats: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+    pmPSF_MaskApTrend (psfTry->psf, PM_PSF_SKYBIAS);
+    psfTry->psf->ApTrend->coeff[0][0][0][0] = poly->coeff[0];
+    psfTry->psf->ApTrend->coeff[0][0][1][0] = 0;
+
+    psfTry->psf->skyBias  = poly->coeff[1];
+    psfTry->psf->ApResid  = poly->coeff[0];
+    psfTry->psf->dApResid = stats->sampleStdev;
+
+    psFree (r2rflux);
     psFree (poly);
     psFree (stats);
 
-    // psFree (daFit);
-    // psFree (daResid);
-
     return true;
 }
+
+/*
+  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
+  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
+  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
+*/
+
Index: /branches/rel10_ifa/psModules/src/objects/pmPSFtry.h
===================================================================
--- /branches/rel10_ifa/psModules/src/objects/pmPSFtry.h	(revision 6447)
+++ /branches/rel10_ifa/psModules/src/objects/pmPSFtry.h	(revision 6448)
@@ -6,6 +6,6 @@
  *  @author EAM, IfA
  *
- *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2005-12-24 01:24:32 $
+ *  @version $Revision: 1.3.4.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-17 17:13:42 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -18,11 +18,11 @@
 
 /**
- *
+ * 
  * This structure contains a pointer to the collection of sources which will
  * be used to test the PSF model form. It lists the pmModelType type of model
  * being tests, and contains an element to store the resulting psf
  * representation. In addition, this structure carries the complete collection of
- * FLT (floating parameter) and PSF (fixed parameter) model fits to each of the
- * sources modelFLT and modelPSF. It also contains a mask which is set by the
+ * EXT (floating parameter) and PSF (fixed parameter) model fits to each of the
+ * sources modelEXT and modelPSF. It also contains a mask which is set by the
  * model fitting and psf fitting steps. For each model, the value of the quality
  * metric is stored in the vector metric and the fitted instrumental magnitude is
@@ -38,9 +38,9 @@
  * ultimate metric to intercompare multiple types of PSF models is the value of
  * the aperture correction scatter.
- *
+ * 
  * XXX: There are many more members in the SDRS then in the prototype code.
  * I stuck with the prototype code.
- *
- *
+ * 
+ * 
  */
 typedef struct
@@ -48,5 +48,5 @@
     pmPSF      *psf;                    ///< Add comment.
     psArray    *sources;                ///< pointers to the original sources
-    psArray    *modelFLT;               ///< model fits, floating parameters
+    psArray    *modelEXT;               ///< model fits, floating parameters
     psArray    *modelPSF;               ///< model fits, PSF parameters
     psVector   *mask;                   ///< Add comment.
@@ -58,13 +58,13 @@
 
 /** pmPSFtryMaskValues
- *
+ * 
  * The following datatype defines the masks used by the pmPSFtry analysis to
  * identify sources which should or should not be included in the analysis.
- *
+ * 
  */
-enum {
+typedef enum {
     PSFTRY_MASK_CLEAR    = 0x00,        ///< Add comment.
     PSFTRY_MASK_OUTLIER  = 0x01,        ///< 1: outlier in psf polynomial fit (provided by psPolynomials)
-    PSFTRY_MASK_FLT_FAIL = 0x02,        ///< 2: flt model failed to converge
+    PSFTRY_MASK_EXT_FAIL = 0x02,        ///< 2: ext model failed to converge
     PSFTRY_MASK_PSF_FAIL = 0x04,        ///< 3: psf model failed to converge
     PSFTRY_MASK_BAD_PHOT = 0x08,        ///< 4: invalid source photometry
@@ -74,7 +74,7 @@
 
 /** pmPSFtryAlloc()
- *
+ * 
  * Allocate a pmPSFtry data structure.
- *
+ * 
  */
 pmPSFtry *pmPSFtryAlloc(
@@ -85,9 +85,9 @@
 
 /** pmPSFtryModel()
- *
+ * 
  * This function takes the input collection of sources and performs a complete
  * analysis to determine a PSF model of the given type (specified by model name).
  * The result is a pmPSFtry with the results of the analysis.
- *
+ * 
  */
 pmPSFtry *pmPSFtryModel(
@@ -99,8 +99,8 @@
 
 /** pmPSFtryMetric()
- *
+ * 
  * This function is used to measure the PSF model metric for the set of
  * results contained in the pmPSFtry structure.
- *
+ * 
  */
 bool pmPSFtryMetric(
