Index: /branches/eam_rel9_p0/psModules/src/astrom/pmConcepts.c
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConcepts.c	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConcepts.c	(revision 5975)
@@ -0,0 +1,455 @@
+#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 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 = 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);
+    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);
+
+    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:
+        return psMetadataItemAllocStr(blank->name, blank->comment,
+                                      pmConceptReadString(fpa, chip, cell, db, blank->name));
+    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
+                         )
+{
+    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 *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
+                        )
+{
+    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(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
+                         )
+{
+    pmConceptsInit();
+    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
+        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
+                       )
+{
+    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
+                      )
+{
+    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
+                       )
+{
+    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
+                        )
+{
+    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
+                       )
+{
+    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
+                        )
+{
+    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
+                        )
+{
+    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
+                       )
+{
+    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
+                        )
+{
+    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
+        pmConceptRegister(psMetadataItemAllocStr("FPA.NAME", "Name of FPA", ""), NULL, NULL,
+                          PM_CONCEPT_LEVEL_FPA);
+
+        // FPA.AIRMASS
+        pmConceptRegister(psMetadataItemAllocF32("FPA.AIRMASS", "Airmass at boresight", 0.0), NULL, NULL,
+                          PM_CONCEPT_LEVEL_FPA);
+
+        // FPA.FILTER
+        pmConceptRegister(psMetadataItemAllocStr("FPA.FILTER", "Filter used", ""), NULL, NULL,
+                          PM_CONCEPT_LEVEL_FPA);
+
+        // FPA.POSANGLE
+        pmConceptRegister(psMetadataItemAllocF32("FPA.POSANGLE", "Position angle of instrument", 0.0), NULL,
+                          NULL, PM_CONCEPT_LEVEL_FPA);
+
+        // FPA.RADECSYS
+        pmConceptRegister(psMetadataItemAllocStr("FPA.RADECSYS", "Celestial coordinate system", ""), NULL,
+                          NULL, PM_CONCEPT_LEVEL_FPA);
+
+        // FPA.RA
+        pmConceptRegister(psMetadataItemAllocF64("FPA.RA", "Right Ascension of boresight", NAN),
+                          (pmConceptReadFunc)pmConceptRead_FPA_RA, (pmConceptWriteFunc)pmConceptWrite_FPA_RA,
+                          PM_CONCEPT_LEVEL_FPA);
+
+        // FPA.DEC
+        pmConceptRegister(psMetadataItemAllocF64("FPA.DEC", "Declination of boresight", NAN),
+                          (pmConceptReadFunc)pmConceptRead_FPA_DEC,
+                          (pmConceptWriteFunc)pmConceptWrite_FPA_DEC, PM_CONCEPT_LEVEL_FPA);
+
+    }
+    if (! conceptsChip) {
+        conceptsChip = psMetadataAlloc();
+        init = true;
+        // XXX Insert here the native Chip concepts
+    }
+    if (! conceptsCell) {
+        conceptsCell = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+
+        // CELL.GAIN
+        pmConceptRegister(psMetadataItemAllocF32("CELL.GAIN", "CCD gain (e/count)", NAN), NULL, NULL,
+                          PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.READNOISE
+        pmConceptRegister(psMetadataItemAllocF32("CELL.GAIN", "CCD read noise (e)", NAN), NULL, NULL,
+                          PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.SATURATION
+        pmConceptRegister(psMetadataItemAllocF32("CELL.SATURATION", "Saturation level (counts)", NAN), NULL,
+                          NULL, PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.BAD
+        pmConceptRegister(psMetadataItemAllocF32("CELL.BAD", "Bad level (counts)", NAN), NULL, NULL,
+                          PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.XPARITY
+        pmConceptRegister(psMetadataItemAllocS32("CELL.XPARITY",
+                          "Orientation in x compared to the rest of the FPA", 0),
+                          NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.YPARITY
+        pmConceptRegister(psMetadataItemAllocS32("CELL.YPARITY",
+                          "Orientation in x compared to the rest of the FPA", 0),
+                          NULL, NULL, PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.READDIR
+        pmConceptRegister(psMetadataItemAllocS32("CELL.READDIR", "Read direction, rows=1, cols=2", 1), NULL,
+                          NULL, PM_CONCEPT_LEVEL_CELL);
+
+
+        // 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
+        pmConceptRegister(psMetadataItemAllocF32("CELL.EXPOSURE", "Exposure time (sec)", NAN), NULL, NULL,
+                          PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.DARKTIME
+        pmConceptRegister(psMetadataItemAllocF32("CELL.DARKTIME", "Time since flush (sec)", NAN), NULL, NULL,
+                          PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.TRIMSEC
+        {
+            psRegion *trimsec = psAlloc(sizeof(psRegion)); // Blank trimsec
+            trimsec->x0 = trimsec->y0 = trimsec->x1 = trimsec->y1 = NAN;
+            pmConceptRegister(psMetadataItemAllocPtr("CELL.TRIMSEC", PS_DATA_UNKNOWN, "Trim section",
+                              trimsec),
+                              (pmConceptReadFunc)pmConceptRead_CELL_TRIMSEC,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_TRIMSEC, PM_CONCEPT_LEVEL_CELL);
+            psFree(trimsec);
+        }
+
+        // CELL.BIASSEC
+        {
+            psList *biassecs = psListAlloc(NULL); // Blank biassecs
+            pmConceptRegister(psMetadataItemAllocPtr("CELL.BIASSEC", PS_DATA_LIST, "Bias sections", biassecs),
+                              (pmConceptReadFunc)pmConceptRead_CELL_BIASSEC,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_TRIMSEC, PM_CONCEPT_LEVEL_CELL);
+            psFree(biassecs);
+        }
+
+        // CELL.XBIN
+        pmConceptRegister(psMetadataItemAllocS32("CELL.XBIN", "Binning in x", 0),
+                          (pmConceptReadFunc)pmConceptRead_CELL_XBIN,
+                          (pmConceptWriteFunc)pmConceptWrite_CELL_XBIN, PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.YBIN
+        pmConceptRegister(psMetadataItemAllocS32("CELL.YBIN", "Binning in y", 0),
+                          (pmConceptReadFunc)pmConceptRead_CELL_YBIN,
+                          (pmConceptWriteFunc)pmConceptWrite_CELL_YBIN, PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.TIMESYS
+        pmConceptRegister(psMetadataItemAllocS32("CELL.TIMESYS", "Time system", -1),
+                          (pmConceptReadFunc)pmConceptRead_CELL_TIMESYS,
+                          (pmConceptWriteFunc)pmConceptWrite_CELL_TIMESYS, PM_CONCEPT_LEVEL_CELL);
+
+        // CELL.TIME
+        {
+            psTime *time = psTimeAlloc(PS_TIME_TAI); // Blank time
+            // Not particularly distinguishing, but should be good enough
+            time->sec = 0;
+            time->nsec = 0;
+            pmConceptRegister(psMetadataItemAlloc("CELL.TIME", PS_DATA_TIME, "Time of exposure", time),
+                              (pmConceptReadFunc)pmConceptRead_CELL_TIME,
+                              (pmConceptWriteFunc)pmConceptWrite_CELL_TIME, PM_CONCEPT_LEVEL_CELL);
+            psFree(time);
+        }
+
+    }
+
+    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/eam_rel9_p0/psModules/src/astrom/pmConcepts.h
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConcepts.h	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConcepts.h	(revision 5975)
@@ -0,0 +1,79 @@
+#ifndef PM_CONCEPTS_H
+#define PM_CONCEPTS_H
+
+#include "pslib.h"
+
+#include "pmAstrometry.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/eam_rel9_p0/psModules/src/astrom/pmConceptsRead.c
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsRead.c	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsRead.c	(revision 5975)
@@ -0,0 +1,387 @@
+#include <stdio.h>
+
+#include "pslib.h"
+
+#include "pmAstrometry.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);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
+
+    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);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
+
+    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);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+    }
+    psTrace(__func__, 7, "Read concept %s (%s): %d\n", name, item->comment, value);
+
+    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);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
+        value = psStringCopy("");
+    }
+    psTrace(__func__, 7, "Read concept %s (%s): %s\n", name, item->comment, value);
+
+    return value;
+}
+
Index: /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsRead.h
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsRead.h	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsRead.h	(revision 5975)
@@ -0,0 +1,65 @@
+#ifndef PM_CONCEPTS_READ_H
+#define PM_CONCEPTS_READ_H
+
+#include "pmAstrometry.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/eam_rel9_p0/psModules/src/astrom/pmConceptsStandard.c
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsStandard.c	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsStandard.c	(revision 5975)
@@ -0,0 +1,999 @@
+#include <stdio.h>
+
+#include "pslib.h"
+
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmAstrometry.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 *secItem = pmConceptRead(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 = pmConceptRead(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
+
+    psMetadataItem *item = psMetadataItemAlloc("CELL.BIASSEC", PS_DATA_LIST, "Bias sections", biassecs);
+    psFree(biassecs);
+    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
+    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");
+    x0item->data.S32 -= fortranCorr(fpa, "CELL.X0");
+    return pmConceptWriteItem(fpa, chip, cell, db, x0item);
+}
+
+bool pmConceptWrite_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
+{
+    psMetadataItem *y0item = psMetadataLookup(cell->concepts, "CELL.Y0");
+    y0item->data.S32 -= fortranCorr(fpa, "CELL.Y0");
+    return pmConceptWriteItem(fpa, chip, cell, db, y0item);
+}
+
Index: /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsStandard.h
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsStandard.h	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsStandard.h	(revision 5975)
@@ -0,0 +1,28 @@
+#ifndef PM_CONCEPTS_STANDARD_H
+#define PM_CONCEPTS_STANDARD_H
+
+#include "pslib.h"
+#include "pmAstrometry.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/eam_rel9_p0/psModules/src/astrom/pmConceptsWrite.c
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsWrite.c	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsWrite.c	(revision 5975)
@@ -0,0 +1,361 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmAstrometry.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) {
+        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
+    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
+    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
+    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
+                       )
+{
+    // 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/eam_rel9_p0/psModules/src/astrom/pmConceptsWrite.h
===================================================================
--- /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsWrite.h	(revision 5975)
+++ /branches/eam_rel9_p0/psModules/src/astrom/pmConceptsWrite.h	(revision 5975)
@@ -0,0 +1,49 @@
+#ifndef PM_CONCEPTS_WRITE_H
+#define PM_CONCEPTS_WRITE_H
+
+#include "pslib.h"
+#include "pmAstrometry.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
