Index: /branches/rel10_ifa/psModules/src/astrom/pmConcepts.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConcepts.c	(revision 6569)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConcepts.c	(revision 6570)
@@ -23,6 +23,6 @@
 
 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
+                                  pmConceptParseFunc parse, // Function to call to parse the concept
+                                  pmConceptFormatFunc format // Function to call to format the concept
                                  )
 {
@@ -31,6 +31,6 @@
 
     spec->blank = psMemIncrRefCounter(blank);
-    spec->read = read;
-    spec->write = write;
+    spec->parse = parse;
+    spec->format = format;
 
     return spec;
@@ -39,6 +39,6 @@
 
 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
+                       pmConceptParseFunc parse, // Function to call to parse the concept
+                       pmConceptFormatFunc format, // Function to call to format the concept
                        pmConceptLevel level // Level at which to store concept in the FPA hierarchy
                       )
@@ -49,5 +49,5 @@
     }
 
-    pmConceptSpec *spec = pmConceptSpecAlloc(blank, read, write); // The concept specification
+    pmConceptSpec *spec = pmConceptSpecAlloc(blank, parse, format); // The concept specification
     psMetadata **target = NULL;         // The metadata of known concepts to write to
     switch (level) {
@@ -74,39 +74,7 @@
 }
 
-// This function gets called for the really boring concepts --- where all you have to do is read from a header
-// or database and you don't need to muck around with conversions.
-// There is no similar "writePlain", since the type is already known.
-static psMetadataItem *readPlain(psMetadataItem *blank, // The blank value with name, comment, type
-                                 pmFPA *fpa, // The FPA of interest
-                                 pmChip *chip, // The Chip of interest
-                                 pmCell *cell, // The cell of interest
-                                 psDB *db // Database handle
-                                )
-{
-    switch (blank->type) {
-    case PS_DATA_STRING: {
-            psString string = pmConceptReadString(fpa, chip, cell, db, blank->name);
-            psMetadataItem *item = psMetadataItemAllocStr(blank->name, blank->comment, string);
-            psFree(string);
-            return item;
-        }
-    case PS_DATA_S32:
-        return psMetadataItemAllocS32(blank->name, blank->comment,
-                                      pmConceptReadS32(fpa, chip, cell, db, blank->name));
-    case PS_DATA_F32:
-        return psMetadataItemAllocF32(blank->name, blank->comment,
-                                      pmConceptReadF32(fpa, chip, cell, db, blank->name));
-    case PS_DATA_F64:
-        return psMetadataItemAllocF64(blank->name, blank->comment,
-                                      pmConceptReadF64(fpa, chip, cell, db, blank->name));
-    default:
-        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) is not of a standard type (%x)\n",
-                 blank->name, blank->comment, blank->type);
-    }
-    return NULL;
-}
 
 // Set all registered concepts to blank value for the specified level
-static bool conceptsBlank(psMetadata **specs,  // One of the concepts specifications
+static bool conceptsBlank(psMetadata *specs,  // One of the concepts specifications
                           psMetadata *target // Place to install the concepts
                          )
@@ -128,9 +96,12 @@
 }
 
+
+
 // Read all registered concepts for the specified level
-static bool conceptsRead(psMetadata **specs, // One of the concepts specifications
+static bool conceptsRead(psMetadata *specs, // One of the concepts specifications
                          pmFPA *fpa,    // The FPA
                          pmChip *chip,  // The chip
                          pmCell *cell,  // The cell
+                         pmConceptSource source, // The source of the concepts to read
                          psDB *db,      // Database handle
                          psMetadata *target // Place into which to read the concepts
@@ -140,36 +111,29 @@
         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);
-            }
-        }
-        if (conceptItem) {
-            psMetadataAddItem(target, conceptItem, PS_LIST_TAIL, PS_META_REPLACE);
-            psFree(conceptItem);        // Drop reference
-        }
-        // No error if conceptItem is NULL, since that may only mean that the required information isn't
-        // present yet.
-    }
-    psFree(specsIter);
+
+    if (source & PM_CONCEPT_SOURCE_CAMERA || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsReadFromCamera(specs, cell, target);
+    }
+    if (source & PM_CONCEPT_SOURCE_DEFAULTS || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsReadFromDefaults(specs, fpa, chip, cell, target);
+    }
+    if (source & PM_CONCEPT_SOURCE_HEADER || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsReadFromHeader(specs, fpa, chip, cell, target);
+    }
+    if (source & PM_CONCEPT_SOURCE_DATABASE || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsReadFromHeader(specs, fpa, chip, cell, db, target);
+    }
+
     return true;
 }
 
 // Write all registered concepts for the specified level
-static bool conceptsWrite(psMetadata **specs, // One of the concepts specifications
+static bool conceptsWrite(psMetadata *specs, // One of the concepts specifications
                           pmFPA *fpa,   // The FPA
                           pmChip *chip, // The chip
                           pmCell *cell, // The cell
+                          pmConceptSource source, // The source of the concepts to write
                           psDB *db,      // Database handle
-                          psMetadata *source // The concepts to write out
+                          psMetadata *concepts // The concepts to write out
                          )
 {
@@ -177,32 +141,21 @@
         pmConceptsInit();
     }
-    if (! fpa->camera) {
-        return false;
-    }
-    psMetadataIterator *iter = psMetadataIteratorAlloc(source, PS_LIST_HEAD, NULL); // Iterator on concepts
-    psMetadataItem *item = NULL;    // Item from the concepts
-    while ((item = psMetadataGetAndIncrement(iter))) {
-        const char *name = item->name;  // Name of the concept
-        if (!strcmp(name, "CELL.NAME") || !strcmp(name, "CHIP.NAME")) {
-            // These concepts are not written out; they are set from things like the FITS extname
-            continue;
-        }
-        psMetadataItem *specItem = psMetadataLookup(*specs, name); // Specification for the concept
-        if (specItem) {
-            pmConceptSpec *spec = specItem->data.V; // The specification
-            if (spec->write) {
-                spec->write(fpa, chip, cell, db); // The concept
-            } else {
-                pmConceptWrite(fpa, chip, cell, db, source, spec->blank->name);
-            }
-        } else {
-            psLogMsg(__func__, PS_LOG_WARN, "Unable to find specification to write concept %s in FPA\n",
-                     name);
-        }
-    }
-    psFree(iter);
+
+    if (source & PM_CONCEPT_SOURCE_CAMERA || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsWriteToCamera(specs, cell, concepts);
+    }
+    if (source & PM_CONCEPT_SOURCE_DEFAULTS || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsWriteToDefaults(specs, fpa, chip, cell, concepts);
+    }
+    if (source & PM_CONCEPT_SOURCE_HEADER || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsWriteToHeader(specs, fpa, chip, cell, concepts);
+    }
+    if (source & PM_CONCEPT_SOURCE_DATABASE || source == PM_CONCEPT_SOURCE_ALL) {
+        pmConceptsWriteToHeader(specs, fpa, chip, cell, db, concepts);
+    }
 
     return true;
 }
+
 
 // Set the concepts for a given FPA to blanks
@@ -211,14 +164,15 @@
 {
     psTrace("psModule.concepts", 5, "Blanking FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
-    return conceptsBlank(&conceptsFPA, fpa->concepts);
+    return conceptsBlank(conceptsFPA, fpa->concepts);
 }
 
 // Read the concepts for a given FPA
 bool pmConceptsReadFPA(pmFPA *fpa,      // FPA for which to read concepts
+                       pmConceptSource source, // The source of the concepts to read
                        psDB *db         // Database handle
                       )
 {
     psTrace("psModule.concepts", 5, "Reading FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
-    return conceptsRead(&conceptsFPA, fpa, NULL, NULL, db, fpa->concepts);
+    return conceptsRead(conceptsFPA, fpa, NULL, NULL, source, db, fpa->concepts);
 }
 
@@ -229,5 +183,5 @@
 {
     psTrace("psModule.concepts", 5, "Writing FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
-    return conceptsWrite(&conceptsFPA, fpa, NULL, NULL, db, fpa->concepts);
+    return conceptsWrite(conceptsFPA, fpa, NULL, NULL, db, fpa->concepts);
 }
 
@@ -237,9 +191,10 @@
 {
     psTrace("psModule.concepts", 5, "Blanking chip concepts: %x %x\n", conceptsChip, chip->concepts);
-    return conceptsBlank(&conceptsChip, chip->concepts);
+    return conceptsBlank(conceptsChip, chip->concepts);
 }
 
 // Read the concepts for a given FPA
 bool pmConceptsReadChip(pmChip *chip,   // Chip for which to read concepts
+                        pmConceptSource source, // The source of the concepts to read
                         psDB *db        // Database handle
                        )
@@ -247,5 +202,5 @@
     psTrace("psModule.concepts", 5, "Reading chip concepts: %x %x\n", conceptsChip, chip->concepts);
     pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
-    return conceptsRead(&conceptsChip, fpa, chip, NULL, db, chip->concepts);
+    return conceptsRead(conceptsChip, fpa, chip, NULL, source, db, chip->concepts);
 }
 
@@ -257,5 +212,5 @@
     psTrace("psModule.concepts", 5, "Writing chip concepts: %x %x\n", conceptsChip, chip->concepts);
     pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
-    return conceptsWrite(&conceptsChip, fpa, chip, NULL, db, chip->concepts);
+    return conceptsWrite(conceptsChip, fpa, chip, NULL, db, chip->concepts);
 }
 
@@ -265,9 +220,10 @@
 {
     psTrace("psModule.concepts", 5, "Blanking cell concepts: %x %x\n", conceptsCell, cell->concepts);
-    return conceptsBlank(&conceptsCell, cell->concepts);
+    return conceptsBlank(conceptsCell, cell->concepts);
 }
 
 // Read the concepts for a given FPA
 bool pmConceptsReadCell(pmCell *cell,   // Cell for which to read concepts
+                        pmConceptSource source, // The source of the concepts to read
                         psDB *db        // Database handle
                        )
@@ -276,5 +232,5 @@
     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);
+    return conceptsRead(conceptsCell, fpa, chip, cell, source, db, cell->concepts);
 }
 
@@ -287,5 +243,5 @@
     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);
+    return conceptsWrite(conceptsCell, fpa, chip, cell, db, cell->concepts);
 }
 
@@ -300,4 +256,5 @@
         // Install the standard concepts
 
+        #if 0
         // FPA.NAME
         {
@@ -306,4 +263,5 @@
             psFree(fpaName);
         }
+        #endif
 
         // FPA.AIRMASS
@@ -340,6 +298,6 @@
         {
             psMetadataItem *fpaRa = psMetadataItemAllocF64("FPA.RA", "Right Ascension of boresight", NAN);
-            pmConceptRegister(fpaRa, (pmConceptReadFunc)pmConceptRead_FPA_RA,
-                              (pmConceptWriteFunc)pmConceptWrite_FPA_RA, PM_CONCEPT_LEVEL_FPA);
+            pmConceptRegister(fpaRa, (pmConceptParseFunc)pmConceptParse_FPA_Coords,
+                              (pmConceptFormatFunc)pmConceptFormat_FPA_Coords, PM_CONCEPT_LEVEL_FPA);
             psFree(fpaRa);
         }
@@ -348,6 +306,6 @@
         {
             psMetadataItem *fpaDec = psMetadataItemAllocF64("FPA.DEC", "Declination of boresight", NAN);
-            pmConceptRegister(fpaDec, (pmConceptReadFunc)pmConceptRead_FPA_DEC,
-                              (pmConceptWriteFunc)pmConceptWrite_FPA_DEC, PM_CONCEPT_LEVEL_FPA);
+            pmConceptRegister(fpaDec, (pmConceptParseFunc)pmConceptParse_FPA_Coords,
+                              (pmConceptFormatFunc)pmConceptFormat_FPA_Coords, PM_CONCEPT_LEVEL_FPA);
             psFree(fpaDec);
         }
@@ -436,5 +394,5 @@
 
         // CELL.DARKTIME
-        {
+        {Read
             psMetadataItem *cellDarktime = psMetadataItemAllocF32("CELL.DARKTIME",
                                            "Time since flush (sec)", NAN);
@@ -450,6 +408,6 @@
                                           "Trim section", trimsec);
             psFree(trimsec);
-            pmConceptRegister(cellTrimsec, (pmConceptReadFunc)pmConceptRead_CELL_TRIMSEC,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_TRIMSEC, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellTrimsec, (pmConceptParseFunc)pmConceptParse_CELL_TRIMSEC,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_TRIMSEC, PM_CONCEPT_LEVEL_CELL);
             psFree(cellTrimsec);
         }
@@ -461,6 +419,6 @@
                                           "Bias sections", biassecs);
             psFree(biassecs);
-            pmConceptRegister(cellBiassec, (pmConceptReadFunc)pmConceptRead_CELL_BIASSEC,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_BIASSEC, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellBiassec, (pmConceptParseFunc)pmConceptParse_CELL_BIASSEC,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_BIASSEC, PM_CONCEPT_LEVEL_CELL);
             psFree(cellBiassec);
         }
@@ -469,6 +427,6 @@
         {
             psMetadataItem *cellXbin = psMetadataItemAllocS32("CELL.XBIN", "Binning in x", 0);
-            pmConceptRegister(cellXbin, (pmConceptReadFunc)pmConceptRead_CELL_XBIN,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_XBIN, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellXbin, (pmConceptParseFunc)pmConceptParse_CELL_Binning,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_Binning, PM_CONCEPT_LEVEL_CELL);
             psFree(cellXbin);
         }
@@ -477,6 +435,6 @@
         {
             psMetadataItem *cellYbin = psMetadataItemAllocS32("CELL.YBIN", "Binning in y", 0);
-            pmConceptRegister(cellYbin, (pmConceptReadFunc)pmConceptRead_CELL_YBIN,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_YBIN, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellYbin, (pmConceptParseFunc)pmConceptParse_CELL_Binning,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_Binning, PM_CONCEPT_LEVEL_CELL);
             psFree(cellYbin);
         }
@@ -485,6 +443,6 @@
         {
             psMetadataItem *cellTimesys = psMetadataItemAllocS32("CELL.TIMESYS", "Time system", -1);
-            pmConceptRegister(cellTimesys, (pmConceptReadFunc)pmConceptRead_CELL_TIMESYS,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_TIMESYS, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellTimesys, (pmConceptParseFunc)pmConceptParse_CELL_TIMESYS,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_TIMESYS, PM_CONCEPT_LEVEL_CELL);
             psFree(cellTimesys);
         }
@@ -499,6 +457,6 @@
                                        "Time of exposure", time);
             psFree(time);
-            pmConceptRegister(cellTime, (pmConceptReadFunc)pmConceptRead_CELL_TIME,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_TIME, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellTime, (pmConceptParseFunc)pmConceptParse_CELL_TIME,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_TIME, PM_CONCEPT_LEVEL_CELL);
             psFree(cellTime);
         }
@@ -507,6 +465,6 @@
         {
             psMetadataItem *cellX0 = psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip", 0);
-            pmConceptRegister(cellX0, (pmConceptReadFunc)pmConceptRead_CELL_X0,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_X0, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellX0, (pmConceptParseFunc)pmConceptParse_CELL_Positions,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_Positions, PM_CONCEPT_LEVEL_CELL);
             psFree(cellX0);
         }
@@ -515,6 +473,6 @@
         {
             psMetadataItem *cellY0 = psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip", 0);
-            pmConceptRegister(cellY0, (pmConceptReadFunc)pmConceptRead_CELL_Y0,
-                              (pmConceptWriteFunc)pmConceptWrite_CELL_Y0, PM_CONCEPT_LEVEL_CELL);
+            pmConceptRegister(cellY0, (pmConceptParseFunc)pmConceptParse_CELL_Positions,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_Positions, PM_CONCEPT_LEVEL_CELL);
             psFree(cellY0);
         }
Index: /branches/rel10_ifa/psModules/src/astrom/pmConcepts.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConcepts.h	(revision 6569)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConcepts.h	(revision 6570)
@@ -6,8 +6,9 @@
 #include "pmFPA.h"
 
-// Function to call to read a concept
-typedef psMetadataItem* (*pmConceptReadFunc)(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-// Function to call to write a concept
-typedef bool (*pmConceptWriteFunc)(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+
+// Function to call to parse a concept once it has been read
+typedef psMetadataItem* (*pmConceptParseFunc)(psMetadataItem *concept, pmConceptSpec *spec, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+// Function to call to format a concept for writing
+typedef psMetadataItem* (*pmConceptFormatFunc)(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
 
 // A "concept" specification
@@ -15,6 +16,6 @@
 {
     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
+    pmConceptParseFunc parse;           // Function to call to read the concept
+    pmConceptFormatFunc format;         // Function to call to write the concept
 }
 pmConceptSpec;
@@ -22,6 +23,6 @@
 // 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
+                                  pmConceptParseFunc parse, // Function to call to parse the concept
+                                  pmConceptFormatFunc format // Function to call to format the concept
                                  );
 
@@ -35,19 +36,17 @@
 // 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
+                       pmConceptParseFunc parse, // Function to call to parse the concept
+                       pmConceptFormatFunc format, // Function to call to format the concept
                        pmConceptLevel level // Level at which to store concept in the FPA hierarchy
                       );
 
-#if 0
 // Some specificity to reading and writing concepts
 typedef enum {
-    PM_CONCEPT_SOURCE_ALL      = 0x00,  // Do all sources
-    PM_CONCEPT_SOURCE_CAMERA   = 0x01,  // Do concepts that come from the camera information
-    PM_CONCEPT_SOURCE_DEFAULTS = 0x02,  // Do concepts that come from defaults
-    PM_CONCEPT_SOURCE_HEADER   = 0x04,  // Do concepts that come from FITS header
-    PM_CONCEPT_SOURCE_DATABASE = 0x08   // Do concepts that come from database
+    PM_CONCEPT_SOURCE_ALL      = 0x00,  // All concepts
+    PM_CONCEPT_SOURCE_CAMERA   = 0x01,  // Concept comes from the camera information
+    PM_CONCEPT_SOURCE_DEFAULTS = 0x02,  // Concept comes from defaults
+    PM_CONCEPT_SOURCE_HEADER   = 0x04,  // Concept comes from FITS header
+    PM_CONCEPT_SOURCE_DATABASE = 0x08   // Concept comes from database
 } pmConceptSource;
-#endif
 
 // Set blanks, read or write concepts at the appropriate level
@@ -55,7 +54,9 @@
                        );
 bool pmConceptsReadFPA(pmFPA *fpa,      // FPA for which to read concepts
+                       pmConceptSource source, // Source for concepts
                        psDB *db         // Database handle
                       );
 bool pmConceptsWriteFPA(pmFPA *fpa,     // FPA for which to write concepts
+                        pmConceptSource source, // Source for concepts
                         psDB *db        // Database handle
                        );
@@ -63,7 +64,9 @@
                         );
 bool pmConceptsReadChip(pmChip *chip,   // Chip for which to read concepts
+                        pmConceptSource source, // Source for concepts
                         psDB *db        // Database handle
                        );
 bool pmConceptsWriteChip(pmChip *chip,  // Chip for which to write concepts
+                         pmConceptSource source, // Source for concepts
                          psDB *db       // Database handle
                         );
@@ -71,7 +74,9 @@
                         );
 bool pmConceptsReadCell(pmCell *cell,   // Cell for which to read concepts
+                        pmConceptSource source, // Source for concepts
                         psDB *db        // Database handle
                        );
 bool pmConceptsWriteCell(pmCell *cell,  // FPA for which to write concepts
+                         pmConceptSource source, // Source for concepts
                          psDB *db       // Database handle
                         );
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.c	(revision 6569)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.c	(revision 6570)
@@ -5,6 +5,425 @@
 #include "pmFPA.h"
 #include "pmConceptsRead.h"
-#include "psAdditionals.h"
-
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+static float parseF32(psMetadataItem *item
+                     )
+{
+    switch (item->type) {
+    case PS_DATA_F32:
+        return item->data.F32;
+    case PS_DATA_F64:
+        // Assume it's OK to truncate to floating point from double
+        return (float)item->data.F64;
+    case PS_DATA_S32:
+        // Promote to float
+        return (float)item->data.S32;
+    default:
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as NaN.\n",
+                item->name, item->comment, item->type);
+        return NAN;
+    }
+}
+
+static double parseF64(psMetadataItem *item
+                      )
+{
+    switch (item->type) {
+    case PS_TYPE_F64:
+        return item->data.F64;
+    case PS_TYPE_F32:
+        // Promote to double
+        return (double)item->data.F32;
+    case PS_TYPE_S32:
+        // Promote to double
+        return (double)item->data.S32;
+    default:
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
+                "--- treating as NaN.\n", item->name, item->comment, item->type);
+        return NAN;
+    }
+}
+
+static int parseS32(psMetadataItem *item
+                   )
+{
+    switch (item->type) {
+    case PS_TYPE_S32:
+        return item->data.S32;
+    case PS_TYPE_F32:
+        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
+                 item->name, comment);
+        return (int)item->data.F32;
+    case PS_TYPE_F64:
+        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
+                 item->name, comment);
+        return (int)item->data.F64;
+    default:
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as zero.\n",
+                item->name, item->comment, item->type);
+        return 0;
+    }
+}
+
+static psString parseString(psMetadataItem *item
+                           )
+{
+    switch (item->type) {
+    case PS_DATA_STRING:
+        return psMemIncrRefCounter(item->data.V);
+    case PS_DATA_F32: {
+            psString value = NULL;    // String to return
+            psStringAppend(&value, "%f", item->data.F32);
+            return value;
+        }
+    case PS_DATA_S32: {
+            psString value = NULL;    // String to return
+            psStringAppend(&value, "%d", item->data.S32);
+            return value;
+        }
+    default:
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
+                "undefined.\n", name, item->comment, item->type);
+        return psStringCopy("");
+    }
+}
+
+
+// This function gets called for the really boring concepts --- where all you have to do is parse from a
+// header or database and you don't need to muck around with conversions.  There is no similar "formatPlain",
+// since the type is already known.
+static psMetadataItem *parsePlain(psMetadataItem *concept, // The concept to parse
+                                  pmConceptSpec *spec // The concept specification
+                                 )
+{
+    psMetadataItem *blank = spec->blank;// The blank specification, which carries the name, comment, type
+    switch (blank->type) {
+    case PS_DATA_STRING: {
+            psString string = parseString(concept); // Get the string, so I can free it after it goes on the MDI
+            psMetadataItem *item = psMetadataItemAllocStr(blank->name, blank->comment, string);
+            psFree(string);
+            return item;
+        }
+    case PS_DATA_S32:
+        return psMetadataItemAllocS32(blank->name, blank->comment, parseS32(concept));
+    case PS_DATA_F32:
+        return psMetadataItemAllocF32(blank->name, blank->comment, parseF32(concept));
+    case PS_DATA_F64:
+        return psMetadataItemAllocF64(blank->name, blank->comment, parseF64(concept));
+    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;
+    }
+}
+
+// Get the lowest HDU
+static pmHDU *getLowestHDU(pmFPA *fpa, // The FPA
+                           pmChip *chip, // The chip, or NULL
+                           pmCell *cell // The cell, or NULL
+                          )
+{
+    pmHDU *hdu = NULL;          // The HDU that's at the lowest level
+    if (cell) {
+        hdu = pmHDUFromCell(cell);
+    } else if (chip) {
+        hdu = pmHDUFromChip(chip);
+    } else if (fpa) {
+        hdu = pmHDUFromFPA(fpa);
+    }
+
+    return hdu;
+}
+
+
+// Parse a single concept
+static bool conceptParse(pmConceptSpec *spec, // The concept specification
+                         psMetadataItem *concept, // The concept to parse
+                         psMetadata *cameraFormat, // The camera format
+                         psMetadata *target, // The target
+                         pmFPA *fpa,    // The FPA
+                         pmChip *chip,  // The chip
+                         pmCell *cell   // The cell
+                        )
+{
+    if (concept) {
+        psMetadataItem *parsed = NULL;  // The parsed concept
+        if (spec->parse) {
+            parsed = spec->parse(concept, spec, cameraFormat, fpa, chip, cell);
+        } else {
+            parsed = parsePlain(concept, spec->blank);
+        }
+
+        // Reformat so that everything's clean
+        if (strcmp(spec->blank->name, parsedItem->name) != 0 ||
+                strcmp(spec->blank->comment, parsedItem->comment) != 0) {
+            psMetadataItem *cleaned = NULL;     // Item that's been cleaned up --- correct name and comment
+            switch (spec->type) {
+            case PS_DATA_STRING:
+                cleaned = psMetadataItemAllocStr(spec->blank->name, spec->blank->comment, concept->data.V);
+                break;
+            case PS_DATA_S32:
+                cleaned = psMetadataItemAllocS32(spec->blank->name, spec->blank->comment, concept->data.S32);
+                break;
+            case PS_DATA_F32:
+                cleaned = psMetadataItemAllocF32(spec->blank->name, spec->blank->comment, concept->data.F32);
+                break;
+            case PS_DATA_F64:
+                cleaned = psMetadataItemAllocF64(spec->blank->name, spec->blank->comment, concept->data.F64);
+                break;
+            default:
+                cleaned = psMetadataItemAlloc(spec->blank->name, concept->type, spec->blank->comment,
+                                              concept->data.V);
+            }
+            psFree(parsed);
+            parsed = cleaned;
+        }
+        psMetadataAddItem(target, parsed, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(parsed);     // Drop reference
+        return true;
+    }
+
+    return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmConceptsReadFromCamera(psMetadata *specs, // The concept specifications
+                              pmCell *cell,  // The cell
+                              psMetadata *target // Place into which to read the concepts
+                             )
+{
+    if (cell) {
+        pmHDU *hdu = getLowestHDU(NULL, NULL, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadata *cellConfig = cell->config; // The camera configuration for this cell
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+            psMetadataItem *conceptItem = psMetadataLookup(cellConfig, name); // The concept, or NULL
+            psMetadataItem *value = NULL; // The value of the concept
+            if (conceptItem) {
+                // Check the SOURCE
+                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+                psStringAppend(nameSource, "%s.SOURCE", name);
+                bool mdok = true;       // Status of MD lookup
+                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+                if (mdok && strlen(source)) {
+                    if (strcasecmp(source, "HEADER") == 0 && conceptItem->type == PS_DATA_STRING) {
+                        value = psMetadataLookup(hdu->header, conceptItem->data.V);
+                    } else if (strcasecmp(source, "VALUE") == 0) {
+                        value = psMemIncrRefCounter(conceptItem);
+                    } else {
+                        psError(PS_ERR_IO, true, "%s isn't HEADER or VALUE --- can't read %s\n", source,
+                                name);
+                        continue;
+                    }
+                } else {
+                    // Assume it's specified by value
+                    value = psMemIncrRefCounter(conceptItem);
+                }
+                conceptParse(spec, value, cameraFormat, target, NULL, NULL, cell);
+            }
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+
+bool pmConceptsReadFromDefaults(psMetadata *specs, // The concept specifications
+                                pmFPA *fpa, // The FPA
+                                pmChip *chip, // The chip
+                                pmCell *cell, // The cell
+                                psMetadata *target // Place into which to read the concepts
+                               )
+{
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
+    if (mdok && defaults) {
+        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+            psMetadataItem *conceptItem = psMetadataLookup(defaults, name); // The concept, or NULL
+            conceptParse(spec, conceptItem, cameraFormat, target, fpa, chip, cell);
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+bool pmConceptsReadFromHeader(psMetadata *specs, // The concept specifications
+                              pmFPA *fpa, // The FPA
+                              pmChip *chip, // The chip
+                              pmCell *cell,  // The cell
+                              psMetadata *target // Place into which to read the concepts
+                             )
+{
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *transSpec = psMetadataLookupMD(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
+    if (mdok && transSpec) {
+        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+            psString keywords = psMetadataLookupStr(&mdok, transSpec, name); // The FITS keywords
+            if (mdok && strlen(keyword) > 0) {
+                // In case there are multiple headers
+                psList *keys = psStringSplit(keywords, " ,;"); // List of keywords
+                psMetadataItem *headerItem = NULL; // The item, to be returned
+                if (keys->n == 1) {
+                    // Only one key --- proceed as usual
+                    headerItem = psMetadataLookup(hdu->header, keywords);
+                } else {
+                    psListIterator *keysIter = psListIteratorAlloc(keys, PS_LIST_HEAD, false); // Iterator
+                    psMetadataItem *key = NULL; // Item from iteration
+                    psList *values = psListAlloc(void); // List containing the values
+                    while ((key = psListGetAndIncrement(keysIter))) {
+                        psMetadataItem *value = psMetadataLookup(hdu->header, key);
+                        psListAdd(values, PS_LIST_TAIL, value);
+                    }
+                    psFree(keysIter);
+                    headerItem = psMetadataItemAlloc(name, PS_DATA_LIST, specItem->comment, values);
+                    psFree(values);
+                }
+                psFree(keys);
+
+                // This will also clean up the name
+                conceptParse(spec, headerItem, cameraFormat, target, fpa, chip, cell);
+            }
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+// XXX --- the below code has NOT been tested!
+bool pmConceptsReadFromDatabase(psMetadata *specs, // The concept specifications
+                                pmFPA *fpa, // The FPA
+                                pmChip *chip, // The chip
+                                pmCell *cell,  // The cell
+                                psDB *db, // The database handle
+                                psMetadata *target // Place into which to read the concepts
+                               )
+{
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *dbSpec = psMetadataLookupMD(&mdok, cameraFormat, "DATABSE"); // The DATABASE spec
+    if (mdok && dbSpec) {
+        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+
+            psMetadata *dbLookup = psMetadataLookupMD(&mdok, dbSpec, name);
+            if (mdok && dbLookup) {
+                const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Table name
+                // Names of the "where" columns
+                const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL");
+                // Values of the "where" columns
+                const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS");
+
+                // Now, need to get the "given"s
+                if (strlen(givenCols) > 0 || strlen(givenPS) > 0) {
+                    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 *conceptItem = 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);
+                            }
+                            conceptItem = (psMetadataItem*)dbResult->data[0];
+                        }
+
+                        // Now we have the result
+                        conceptParse(spec, headerItem, cameraFormat, target, fpa, chip, cell);
+
+                    }
+                    psFree(cols);
+                    psFree(values);
+                }
+            } // Doing the "given"s.
+
+        } // Iterating through the concept specifications
+        psFree(specsIter);
+
+        return true;
+    }
+    return false;
+}
+
+
+
+
+#if 0
 
 psMetadataItem *pmConceptReadFromCamera(pmCell *cell, // The cell
@@ -241,139 +660,3 @@
 
 
-float pmConceptReadF32(pmFPA *fpa,        // The FPA
-                       pmChip *chip,      // The chip
-                       pmCell *cell,      // The cell
-                       psDB *db,          // DB handle
-                       const char *name // Name of the concept
-                      )
-{
-    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
-    float value = NAN;
-    if (item) {
-        switch (item->type) {
-        case PS_DATA_F32:
-            value = item->data.F32;
-            break;
-        case PS_DATA_F64:
-            // Assume it's OK to truncate to floating point from double
-            value = (float)item->data.F64;
-            break;
-        case PS_DATA_S32:
-            // Promote to float
-            value = (float)item->data.S32;
-            break;
-        default:
-            psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
-                    "undefined.\n", name, item->comment, item->type);
-        }
-        psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
-    } else {
-        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
-    }
-
-    return value;
-}
-
-double pmConceptReadF64(pmFPA *fpa,   // The FPA
-                        pmChip *chip, // The chip
-                        pmCell *cell, // The cell
-                        psDB *db,     // DB handle
-                        const char *name // Name of the concept
-                       )
-{
-    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
-    double value = NAN;
-    if (item) {
-        switch (item->type) {
-        case PS_TYPE_F64:
-            value = item->data.F64;
-            break;
-        case PS_TYPE_F32:
-            // Promote to double
-            value = (double)item->data.F32;
-            break;
-        case PS_TYPE_S32:
-            // Promote to double
-            value = (double)item->data.S32;
-            break;
-        default:
-            psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
-                    "--- treating as undefined.\n", name, item->comment, item->type);
-        }
-        psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
-    } else {
-        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
-    }
-
-    return value;
-}
-
-int pmConceptReadS32(pmFPA *fpa,   // The FPA
-                     pmChip *chip, // The chip
-                     pmCell *cell, // The cell
-                     psDB *db,     // DB handle
-                     const char *name // Name of the concept
-                    )
-{
-    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
-    int value = 0;
-    if (item) {
-        switch (item->type) {
-        case PS_TYPE_S32:
-            value = item->data.S32;
-            break;
-        case PS_TYPE_F32:
-            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
-                     name, comment);
-            value = (int)item->data.F32;
-            break;
-        case PS_TYPE_F64:
-            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
-                     name, comment);
-            value = (int)item->data.F64;
-            break;
-        default:
-            psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
-                    "undefined.\n", name, item->comment, item->type);
-        }
-        psTrace(__func__, 7, "Read concept %s (%s): %d\n", name, item->comment, value);
-    } else {
-        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
-    }
-
-    return value;
-}
-
-psString pmConceptReadString(pmFPA *fpa, // The FPA
-                             pmChip *chip, // The chip
-                             pmCell *cell, // The cell
-                             psDB *db,  // DB handle
-                             const char *name // Name of the concept
-                            )
-{
-    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
-    psString value = NULL;
-    if (item) {
-        switch (item->type) {
-        case PS_DATA_STRING:
-            value = psMemIncrRefCounter(item->data.V);
-            break;
-        case PS_DATA_F32:
-            psStringAppend(&value, "%f", item->data.F32);
-            break;
-        case PS_DATA_S32:
-            psStringAppend(&value, "%d", item->data.S32);
-            break;
-        default:
-            psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
-                    "undefined.\n", name, item->comment, item->type);
-        }
-        psTrace(__func__, 7, "Read concept %s (%s): %s\n", name, item->comment, value);
-    } else {
-        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
-        value = psStringCopy("");
-    }
-
-    return value;
-}
-
+#endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.c	(revision 6569)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.c	(revision 6570)
@@ -16,175 +16,111 @@
 
 
-// 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
+static double defaultCoordScaling(psMetadataItem *pattern)
+{
+    if (strcmp(pattern->name, "FPA.RA") == 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "Assuming format for %s is HOURS.\n", pattern->name);
+        return M_PI / 12.0;
+    } else if (strcmp(pattern->name, "FPA.DEC") == 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "Assuming format for %s is DEGREES.\n", pattern->name);
+        return M_PI / 180.0;
+    } else {
+        psAbort("Should never ever get here.\n");
+    }
+}
+
+
+// FPA.RA and FPA.DEC
+psMetadataItem *pmConceptParse_FPA_Coords(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+    assert(cameraFormat);
+
+    double coords = NAN;                // The coordinates
+    switch (concept->type) {
+    case PS_TYPE_F32:
+        coords = concept->data.F32;
+        break;
+    case PS_TYPE_F64:
+        coords = concept->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(concept->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                    sscanf(concept->data.V, "%d %d %f", &big, &medium, &small) != 3)
             {
-                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
+                psError(PS_ERR_IO, true, "Cannot interpret FPA.RA: %s\n", concept->data.V);
+                break;
+            }
+            coords = abs(big) + (float)medium/60.0 + small/3600.0;
+            if (big < 0)
             {
-                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");
+                coords *= -1.0;
+            }
+        }
+        break;
+    default:
+        psError(PS_ERR_IO, true, "%s concept is of an unexpected type: %x\n", pattern->name, concept->type);
+        return NULL;
+    }
+
+    // How to interpret the coordinates
+    bool mdok = true;           // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "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) {
+        psString format = psMetadataLookupStr(&mdok, formats, blank->name);
+        if (mdok && strlen(format) > 0) {
+            if (strcasecmp(format, "HOURS") == 0) {
+                coords *= M_PI / 12.0;
+            } else if (strcasecmp(format, "DEGREES") == 0) {
+                coords *= M_PI / 180.0;
+            } else if (strcasecmp(format, "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;
+                coords *= defaultCoordScaling(pattern);
             }
         } else {
-            psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
-            ra /= M_PI / 12.0;
+            coords *= defaultCoordScaling(pattern);
         }
     } 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;
+        coords *= defaultCoordScaling(pattern);
+    }
+
+    return psMetadataItemAllocF64(pattern->name, pattern->comment, coords);
+}
+
+// FPA.RA and FPA.DEC
+psMetadataItem *pmConceptFormat_FPA_Coords(psMetadataItem *concept, psMetadataItem *pattern, pmConceptSpec *spec, psMetadata *cameraFormat)
+{
+    assert(concept);
+    assert(pattern);
+    assert(cameraFormat);
+
+    double coords = concept->data.F64;  // The coordinates
+
+    // How to interpret the coordinates
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, pattern->name);
+        if (mdok && strlen(format) > 0) {
+            if (strcasecmp(format, "HOURS") == 0) {
+                coords /= M_PI / 12.0;
+            } else if (strcasecmp(format, "DEGREES") == 0) {
+                coords /= M_PI / 180.0;
+            } else if (strcasecmp(format, "RADIANS") == 0) {
+                // No action required
+            } else {
+                coords /= defaultCoordScaling(pattern);
+            }
+        } else {
+            coords /= defaultCoordScaling(pattern);
+        }
+    } else {
+        coords /= defaultCoordScaling(pattern);
     }
 
@@ -193,259 +129,123 @@
     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)
-{
+    medium = (int)(60.0*(coords - (double)big));
+    small = 3600.0*(coords - (double)big - 60.0 * (double)medium);
+    psString coordString = NULL;        // String with the coordinates in sexagesimal format
+    psStringAppend(&coordString, "%d:%d:%.2f", big, medium, small);
+    psMetadataItem *coordItem = psMetadataItemAllocStr(pattern->name, pattern->comment, coordString);
+    psFree(coordString);
+
+    return coordItem;
+}
+
+
+psMetadataItem *pmConceptParse_CELL_TRIMSEC(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cell);
+    assert(pattern);
+
+    psMetadata *cellConfig = cell->config; // The cell configuration
     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);
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC after read is not of type STR (%x)\n", concept->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);
+        *trimsec = psRegionFromString(concept->data.V);
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_UNKNOWN, pattern->comment, trimsec);
     psFree(trimsec);
     return item;
 }
 
-
-psMetadataItem *pmConceptRead_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
+psMetadataItem *pmConceptParse_CELL_BIASSEC(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cell);
+    assert(pattern);
+    psMetadata *cellConfig = cell->config; // The cell configuration
+
     psList *biassecs = psListAlloc(NULL); // List of bias sections
     psMetadataItem *item = psMetadataItemAlloc("CELL.BIASSEC", PS_DATA_LIST, "Bias sections", biassecs);
     psFree(biassecs);               // Drop reference
 
-    psMetadataItem *secItem = pmConceptRead(fpa, chip, cell, db, "CELL.BIASSEC");
-    if (! secItem) {
-        psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.\n");
-        return item;
-    }
-    if (secItem->type != PS_DATA_STRING) {
-        psError(PS_ERR_IO, true, "CELL.BIASSEC is not of type STR (%x)\n", secItem->type);
-        return item;
-    }
-
-    psString sections = secItem->data.V; // The section string
-
-    psMetadataItem *sourceItem = pmConceptRead(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
-    if (! sourceItem) {
-        psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
-        return item;
-    } else if (sourceItem->type != PS_DATA_STRING) {
-        psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
-        return item;
-    }
-
-    psString source = sourceItem->data.V; // The source string
-
-    if (! strcasecmp(source, "NONE")) {
-        return item;                // There is no biassec
-    }
-
-    psList *secList = psStringSplit(sections, " ;"); // List of sections
-    psListIterator *secIter = psListIteratorAlloc(secList, PS_LIST_HEAD, false); // Iterator over
-    // sections
-    psString aSection = NULL; // A section from the list
-    while ((aSection = psListGetAndIncrement(secIter))) {
-        psRegion *region = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by
-        // value)
-
-        if (strcasecmp(source, "VALUE") == 0) {
-            *region = psRegionFromString(aSection);
-        } else if (strcasecmp(source, "HEADER") == 0) {
-            psMetadata *header = NULL; // The FITS header
-            if (cell->hdu) {
-                header = cell->hdu->header;
-            } else if (chip->hdu) {
-                header = chip->hdu->header;
-            } else if (fpa->hdu) {
-                header = fpa->hdu->header;
-            }
-            if (! header) {
-                psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
-                *region = psRegionSet(0.0,0.0,0.0,0.0);
+    switch (concept->type) {
+    case PS_DATA_STRING: {
+            psList *regions = psStringSplit(concept->data.V, " ;"); // List of regions
+            psListIterator *regionsIter = psListIteratorAlloc(regions, PS_LIST_HEAD, false); // Iterator
+            psString regionString = NULL; // Region string from iteration
+            while ((regionString = psListGetAndIncrement(regionsIter))) {
+                psRegion *region = psAlloc(sizeof(psRegion)); // The region
+                *region = psRegionFromString(regionString);
+                psListAdd(biassecs, PS_LIST_TAIL, region);
+                psFree(region);           // Drop reference
+            }
+            psFree(regionsIter);
+            psFree(regions);
+            break;
+        }
+    case PS_DATA_LIST: {
+            psList *regions = concept->data.V; // The list of regions
+            psListIterator *regionsIter = psListIteratorAlloc(regions, PS_LIST_HEAD, false); // Iterator
+            psMetadataItem *regionItem = NULL; // Item from list iteration
+            while ((regionItem = psListGetAndIncrement(regionsIter))) {
+                if (regionItem->type != PS_DATA_STRING) {
+                    psLogMsg(__func__, PS_LOG_WARN, "CELL.BIASSEC member is not of type STR --- ignored.\n");
+                    continue;
+                }
+                psRegion *region = psAlloc(sizeof(psRegion)); // The region
+                *region = psRegionFromString(regionItem->data.V);
+                psListAdd(biassecs, PS_LIST_TAIL, region);
+                psFree(region);           // Drop reference
+            }
+            psFree(regionsIter);
+            break;
+        }
+    default:
+        psError(PS_ERR_IO, true, "CELL.BIASSEC after read is not of type STRING or LIST --- assuming "
+                "blank.\n");
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_LIST, pattern->comment, biassecs);
+    psFree(biassecs);
+    return item;
+}
+
+// CELL.XBIN and CELL.YBIN
+psMetadataItem *pmConceptParse_CELL_BINNING(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+
+    int binning = 1;                    // Binning factor in x
+    if (concept->type == PS_DATA_STRING) {
+        psString binString = concept->data.V; // The string containing the binning
+        if ((strcmp(pattern->name, "CELL.XBIN") == 0 && sscanf(binString, "%d %*d", &binning) != 1 &&
+                sscanf(binString, "%d,%*d", &binning) != 1) ||
+                (strcmp(pattern->name, "CELL.YBIN") == 0 && sscanf(binString, "%*d %d", &binning) != 1 &&
+                 sscanf(binString, "%*d,%d", &binning) != 1) {
+                psError(PS_ERR_IO, true, "Unable to parse string to get %s: %s\n", pattern->name, binString)
+                    ;
+                }
+            } else if (concept->type == PS_TYPE_S32) {
+                binning = concept->data.S32;
             } else {
-                bool mdok = true;           // Status of MD lookup
-                psString secValue = psMetadataLookupStr(&mdok, header, aSection);
-                if (! mdok || ! secValue) {
-                    psError(PS_ERR_IO, false, "Unable to locate header %s\n", aSection);
-                    *region = psRegionSet(0.0,0.0,0.0,0.0);
-                } else {
-                    *region = psRegionFromString(secValue);
-                }
-            }
-        } else {
-            psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
-                    "VALUE.\n", source);
-            *region = psRegionFromString(aSection);
-        } // Value of CELL.BIASSEC.SOURCE
-
-        psListAdd(biassecs, PS_LIST_TAIL, region);
-        psFree(region);
-    } // Iterating over multiple sections
-    psFree(secIter);
-    psFree(secList);
-
-    return item;
-}
-
-
-psMetadataItem *pmConceptRead_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
-    int xBin = 1;                   // Binning factor in x
-    psMetadataItem *binItem = pmConceptRead(fpa, chip, cell, db, "CELL.XBIN");
-    if (! binItem) {
-        psError(PS_ERR_IO, false, "Couldn't find CELL.XBIN.\n");
-    } else if (binItem->type == PS_DATA_STRING) {
-        psString binString = binItem->data.V; // The string containing the binning
-        if (sscanf(binString, "%d %*d", &xBin) != 1 &&
-                sscanf(binString, "%d,%*d", &xBin) != 1) {
-            psError(PS_ERR_IO, true, "Unable to read string to get x binning: %s\n", binString);
-        }
-    } else if (binItem->type == PS_TYPE_S32) {
-        xBin = binItem->data.S32;
-    } else {
-        psError(PS_ERR_IO, true, "Note sure how to interpret CELL.XBIN of type %x --- assuming 1.\n",
-                binItem->type);
-    }
-
-    return psMetadataItemAllocS32("CELL.XBIN", "Binning in x", xBin);
-}
-
-psMetadataItem *pmConceptRead_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
-    int yBin = 1;                   // Binning factor in y
-    psMetadataItem *binItem = pmConceptRead(fpa, chip, cell, db, "CELL.YBIN");
-    if (! binItem) {
-        psError(PS_ERR_IO, false, "Couldn't find CELL.YBIN.\n");
-    } else if (binItem->type == PS_DATA_STRING) {
-        psString binString = binItem->data.V; // The string containing the binning
-        if (sscanf(binString, "%*d %d", &yBin) != 1 &&
-                sscanf(binString, "%*d,%d", &yBin) != 1) {
-            psError(PS_ERR_IO, true, "Unable to read string to get y binning: %s\n", binString);
-        }
-    } else if (binItem->type == PS_TYPE_S32) {
-        yBin = binItem->data.S32;
-    } else {
-        psError(PS_ERR_IO, true, "Note sure how to interpret CELL.YBIN of type %x --- assuming 1.\n",
-                binItem->type);
-    }
-
-    return psMetadataItemAllocS32("CELL.YBIN", "Binning in y", yBin);
-}
-
-psMetadataItem *pmConceptRead_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
+                psError(PS_ERR_IO, true, "Note sure how to parse %s of type %x --- assuming 1.\n", pattern->name,
+                        binItem->type);
+            }
+
+            return psMetadataItemAllocS32(pattern->name, pattern->comment, binning);
+}
+
+
+psMetadataItem *pmConceptParse_CELL_TIMESYS(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+
     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) {
+    if (concept->type != PS_DATA_STRING || strlen(sys) <= 0) {
         psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
     } else if (strcasecmp(sys, "TAI") == 0) {
@@ -462,10 +262,14 @@
 
     psFree(sys);
-    return psMetadataItemAllocS32("CELL.TIMESYS", "Time system", timeSys);
-}
-
-
-psMetadataItem *pmConceptRead_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, timeSys);
+}
+
+
+psMetadataItem *pmConceptParse_CELL_TIME(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+    assert(cell);
+
     // Need CELL.TIMESYS first
     bool mdok = false;                  // Result of MD lookup
@@ -475,148 +279,129 @@
         timeSys = PS_TIME_UTC;
     }
+
+    // Get format
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS");
+    if (!mdok || !formats) {
+        psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
+        return NULL;
+    }
+
+    psString timeFormat = psMetadataLookupStr(&mdok, formats, "CELL.TIME");
+    if (!mdok || strlen(timeFormat) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
+        return NULL;
+    }
+
     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 (concept->type) {
+    case PS_DATA_LIST: {
+            // The date and time are stored separately
+            // Assume the date is first and the time second
+            psList *dateTime = concept->data.V; // The list containing items for date and time
+            psMetadataItem *dateItem = psListGet(dateTime, PS_LIST_HEAD); // Item containing the date
+            psMetadataItem *timeItem = psListGet(dateTime, PS_LIST_HEAD + 1); // Item containing the time
+            if (dateItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Date is not of type STR.\n");
+                return NULL;
+            }
+            psString dateString = dateItem->data.V; // The string with the date
+            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);
+                return NULL;
+            }
+            if (strstr(timeFormat, "BACKWARDS")) {
+                int temp = day;
+                day = year;
+                year = temp;
+            }
+            if (strstr(timeFormat, "PRE2000") || year < 2000) {
+                year += 2000;
+            }
+
+            psString timeString = NULL; // The string with the time
+            if (timeItem->type == PS_DATA_STRING) {
+                timeString = timeItem->data.V;
+            } else {
+                // Assume that time is specified in Second of Day (!)
+                double seconds = NAN;
                 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
-                    }
+                case PS_TYPE_S32:
+                    seconds = timeItem->data.S32;
                     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);
-                        }
-                    }
+                case PS_TYPE_F32:
+                    seconds = timeItem->data.F32;
                     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);
-                        }
-                    }
+                case PS_TYPE_F64:
+                    seconds = timeItem->data.F64;
                     break;
                 default:
-                    psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
+                    psError(PS_ERR_IO, true, "Time is not of an expected type: %x\n", timeItem->type);
+                    return NULL;
                 }
+                // 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(&timeString, "%02d:%02d:%02f", hours, minutes, seconds);
+            }
+            psString dateTimeString = NULL;
+            psStringAppend(&dateTimeString, "%sT%s", dateString, timeString);
+            time = psTimeFromISO(dateTimeString, timeSys);
+            break;
+        }
+    case PS_DATA_STRING: {
+            psString timeString = concept->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 {
-                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);
+                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)concept->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)concept->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");
+        return NULL;
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_TIME, pattern->comment, time);
     psFree(time);
     return item;
@@ -624,10 +409,10 @@
 
 // Correct a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
-static int fortranCorr(pmFPA *fpa,       // FPA, contains the camera configuration
+static int fortranCorr(psMetadata *cameraFormat, // The camera format description
                        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");
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS");
     if (mdok && formats) {
         psString format = psMetadataLookupStr(&mdok, formats, name);
@@ -639,239 +424,105 @@
 }
 
-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->config, "CELL.TRIMSEC");
-        psRegion checkRegion = psRegionFromString(checkString);
-        if (! COMPARE_REGIONS(&checkRegion, trimsec)) {
-            psError(PS_ERR_IO, true, "Target CELL.TRIMSEC is specified by value, and values don't "
-                    "match.\n");
-            return false;
-        }
-        return true;
-    }
-
-    if (strcasecmp(source, "HEADER") == 0) {
-        psMetadata *header = NULL; // The FITS header
-        if (cell->hdu) {
-            header = cell->hdu->header;
-        } else if (chip->hdu) {
-            header = chip->hdu->header;
-        } else if (fpa->hdu) {
-            header = fpa->hdu->header;
-        }
-        if (! header) {
-            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
-            return false;
-        }
-        psMetadataAddItem(header, trimsecItem, PS_LIST_TAIL, PS_META_REPLACE);
-        return true;
-    }
-
-    psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
-    return false;
-}
-
-bool pmConceptWrite_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
-    psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
-    psList *biassecs = biassecItem->data.V; // The biassecs region list
-
-    if (biassecs->n == 0) {
-        psMetadataItem *noneItem = psMetadataItemAllocStr("CELL.BIASSEC", "Bias section", "NONE");
-        pmConceptWriteItem(fpa, chip, cell, db, noneItem);
-        return true;
-    }
-
-    psString source = pmConceptReadString(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE"); // The source string
-
-    if (strcasecmp(source, "VALUE") == 0) {
-        // Check that it's the same value as stored in the camera
-        psString checkString = psMetadataLookupStr(NULL, cell->config, "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->config, "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) {
+psMetadataItem *pmConceptParse_CELL_Positions(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    if (concept->type != PS_TYPE_S32) {
+        psError(PS_ERR_IO, true, "Concept %s is not of type S32, as expected.\n", pattern->name);
+        return NULL;
+    }
+    int offset = concept->data.S32;
+    offset += fortranCorr(cameraFormat, pattern->name);
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, offset);
+}
+
+
+
+psMetadataItem *pmConceptFormat_CELL_TRIMSEC(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+
+    psRegion *trimsec = concept->data.V; // The trimsec region
+    psString trimsecString = psRegionToString(*trimsec);
+    psMetadataItem *formatted = psMetadataItemAllocStr(trimsecItem->name, trimsecItem->comment,
+                                trimsecString);
+    psFree(trimsecString);
+    return formatted;
+}
+
+
+bool pmConceptFormat_CELL_BIASSEC(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    psList *biassecs = concept->data.V; // The biassecs region list
+    psString biassecString = NULL;      // String containing the biassecs
+    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+    psRegion *region = NULL;            // Region from iteration
+    bool first = true;                  // Are we on the first one?
+    while ((region = psListGetAndIncrement(biassecsIter))) {
+        psString regionString = psRegionToString(*region); // The string region "[x0:x1,y0:y1]"
+        if (first) {
+            psStringAppend(&biassecString, "%s", regionString);
+            first = false;
+        } else {
+            psStringAppend(&biassecString, ";%s", regionString); // Put in a semi-colon
+        }
+        psFree(regionString);
+    }
+    psFree(biassecsIter);
+    psMetadataItem *formatted = psMetadataItemAllocStr(concept->name, concept->comment, biassecString);
+    psFree(biassecString);              // Drop reference
+    return formatted;
+}
+
+
+// This function actually does both CELL.XBIN and CELL.YBIN if CELL.XBIN and CELL.YBIN are specified by the
+// same header.
+psMetadataItem *pmConceptFormat_CELL_XBIN(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+
+    psMetadata *translation = psMetadataLookupMD(NULL, cameraFormat, "TRANSLATION");
+    bool xBinOK = true, yBinOK = true;  // Status of MD lookups
+    psString xKeyword = psMetadataLookupStr(&xBinOK, translation, "CELL.XBIN");
+    psString yKeyword = psMetadataLookupStr(&yBinOK, translation, "CELL.YBIN");
+    if (xBinOK && yBinOK && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+            strcasecmp(xKeyword, yKeyword) == 0) {
+        psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y
         psString binString = psStringCopy("");
-        psStringAppend(&binString, "%d,%d", xBinItem->data.S32, yBinItem->data.S32);
-        psMetadataItem *binItem = psMetadataItemAllocStr(xKeyword, "Binning factor in x and y", binString);
+        psStringAppend(&binString, "%d,%d", concept->data.S32, yBinItem->data.S32);
+        psMetadataItem *binItem = psMetadataItemAllocStr(concept->name, concept->comment, 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");
+        return binItem;
+    }
+
+    // Otherwise, there's no formatting required
+    return concept;
+}
+
+// Only need to format if both if CELL.XBIN and CELL.YBIN are not specified by the same header.
+psMetadataItem *pmConceptFormat_CELL_YBIN(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+
+    psMetadata *translation = psMetadataLookupMD(NULL, cameraFormat, "TRANSLATION");
+    bool xBinOK = true, yBinOK = true;  // Status of MD lookups
+    psString xKeyword = psMetadataLookupStr(&xBinOK, translation, "CELL.XBIN");
+    psString yKeyword = psMetadataLookupStr(&yBinOK, translation, "CELL.YBIN");
+    if (xBinOK && yBinOK && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+            strcasecmp(xKeyword, yKeyword) == 0) {
+        // Censor this --- it's already done (though no harm if it's done twice
+        return NULL;
+    }
+
+    // No formatting required
+    return concept;
+}
+
+
+psMetadataItem *pmConceptFormat_CELL_TIMESYS(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
     psString sys = NULL;            // String to store
-    switch (sysItem->data.S32) {
+    switch (concept->data.S32) {
     case PS_TIME_TAI:
         sys = psStringCopy("TAI");
@@ -889,7 +540,5 @@
         sys = psStringCopy("Unknown");
     }
-    psMetadataItem *newItem = psMetadataItemAllocStr("CELL.TIMESYS", "Time system", sys);
-    bool success = pmConceptWriteItem(fpa, chip, cell, db, newItem);
-    psFree(newItem);
+    psMetadataItem *newItem = psMetadataItemAllocStr(concept->name, concept->comment, sys);
     psFree(sys);
 
@@ -897,121 +546,76 @@
 }
 
-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");
+psMetadataItem *pmConceptFormat_CELL_TIME(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    psTime *time = concept->data.V;     // The time
+    psMetadata *formats = psMetadataLookupMD(NULL, cameraFormat, "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);
+    if (strlen(format) == 0 || strcasecmp(format, "ISO") == 0) {
+        psString dateTimeString = psTimeToISO(time); // String representation
+        psMetadataItem *item = psMetadataItemAllocStr(concept->name, concept->comment, dateTimeString);
         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);
+        return item;
+    }
+    if (strstr(format, "SEPARATE")) {
+        // We're working with two separate headers --- construct a list with the date and time separately
+        psString dateTimeString = psTimeToISO(time); // String representation
+        psList *dateTime = psStringSplit(dateTimeString, "T");
         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);
-
+        psString dateString = psListGet(dateTime, PS_LIST_HEAD); // The date string
+        psString timeString = psListGet(dateTime, PS_LIST_TAIL); // The time string
+
+        // Need to format the strings....
         // 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;
+        psMetadataItem *dateItem = psMetadataItemAllocStr(concept->name, "The date of observation",
+                                   dateString);
+        psMetadataItem *timeItem = psMetadataItemAllocStr(concept->name, "The time of observation",
+                                   timeString);
+        psFree(dateString);
+        psFree(timeString);
+
+        psListRemove(dateTime, PS_LIST_HEAD);
+        psListRemove(dateTime, PS_LIST_HEAD);
+        psListAdd(dateTime, PS_LIST_HEAD, dateItem);
+        psListAdd(dateTime, PS_LIST_TAIL, timeItem);
+
+        psMetadataItem *item = psMetadataItemAllocPtr(concept->name, PS_DATA_LIST, concept->comment,
+                               dateTime);
+        return item;
     }
     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;
+        return psMetadataItemAllocF64(concept->name, concept->comment, mjd);
     }
     if (strcasecmp(format, "JD") == 0) {
         double jd = psTimeToMJD(time);
-        psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
-        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
-        psFree(newTimeItem);
-        psFree(dateTimeString);
-        return success;
-    }
-
-    psError(PS_ERR_IO, true, "Not sure how to write concept CELL.TIME (%s)\n", dateTimeString);
-    return false;
-}
-
-bool pmConceptWrite_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
-    psMetadataItem *x0item = psMetadataLookup(cell->concepts, "CELL.X0");
-    psMetadataItem *newItem = psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip",
-                              x0item->data.S32 - fortranCorr(fpa, "CELL.X0"));
-    return pmConceptWriteItem(fpa, chip, cell, db, newItem);
-}
-
-bool pmConceptWrite_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
-{
-    psMetadataItem *y0item = psMetadataLookup(cell->concepts, "CELL.Y0");
-    psMetadataItem *newItem = psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip",
-                              y0item->data.S32 - fortranCorr(fpa, "CELL.Y0"));
-    return pmConceptWriteItem(fpa, chip, cell, db, newItem);
-}
-
+        return psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
+    }
+
+    psError(PS_ERR_IO, true, "Not sure how to format concept CELL.TIME (%s)\n", dateTimeString);
+    return NULL;
+}
+
+psMetadataItem *pmConceptFormat_CELL_Positions(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell *db)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    if (concept->type != PS_TYPE_S32) {
+        psError(PS_ERR_IO, true, "Concept %s is not of type S32, as expected.\n", pattern->name);
+        return NULL;
+    }
+    int offset = concept->data.S32;
+    offset -= fortranCorr(cameraFormat, concept->name);
+    return psMetadataItemAllocS32(concept->name, concept->comment, offset);
+}
+
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.h
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.h	(revision 6569)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsStandard.h	(revision 6570)
@@ -5,24 +5,19 @@
 #include "pmFPA.h"
 
-psMetadataItem *pmConceptRead_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-psMetadataItem *pmConceptRead_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
-bool pmConceptWrite_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db);
+psMetadataItem *pmConceptParse_FPA_Coords(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_FPA_Coords(psMetadataItem *concept, psMetadataItem *pattern, pmConceptSpec *spec, psMetadata *cameraFormat);
+psMetadataItem *pmConceptParse_CELL_TRIMSEC(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_BIASSEC(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_BINNING(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_TIMESYS(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_TIME(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_Positions(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_TRIMSEC(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+bool pmConceptFormat_CELL_BIASSEC(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_XBIN(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_YBIN(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_TIMESYS(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_TIME(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_Positions(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell *db);
 
 #endif
Index: /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.c
===================================================================
--- /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.c	(revision 6569)
+++ /branches/rel10_ifa/psModules/src/astrom/pmConceptsWrite.c	(revision 6570)
@@ -7,4 +7,7 @@
 #include "psAdditionals.h"
 
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 static bool compareConcepts(psMetadataItem *item1, // First item to compare
@@ -67,4 +70,321 @@
 }
 
+// Format a single concept
+static psMetadataItem *conceptFormat(pmConceptSpec *spec, // The concept specification
+                                     psMetadataItem *concept, // The concept to parse
+                                     psMetadata *cameraFormat, // The camera format
+                                     pmFPA *fpa, // The FPA
+                                     pmChip *chip, // The chip
+                                     pmCell *cell // The cell
+                                    )
+{
+    if (concept) {
+        psMetadataItem *formatted = NULL;  // The formatted concept
+        if (spec->format) {
+            formatted = spec->format(concept, cameraFormat, fpa, chip, cell);
+        } else {
+            formatted = psMemIncrRefCounter(concept);
+        }
+
+        return formatted;
+    }
+}
+
+static bool writeHeader(pmHDU *hdu,     // HDU for which to add to the header
+                        const char *keyword, // Keyword to add
+                        psMetadataItem *item // Item to add to the header
+                       )
+{
+    switch (item->type) {
+    case PS_DATA_STRING:
+        return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.V);
+    case PS_DATA_S32:
+        return psMetadataAddS32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.S32);
+    case PS_DATA_F32:
+        return psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.F32);
+    case PS_DATA_F64:
+        return psMetadataAddF64(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.F64);
+    default:
+        psLogMsg(__func__, PS_LOG_WARN, "Type of %s is not suitable for a FITS header --- not added.\n",
+                 item->name);
+        return false;
+    }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmConceptsWriteToCamera(psMetadata *specs, // The concept specifications
+                             pmCell *cell,   // The cell
+                             psMetadata *concepts // The concepts
+                            )
+{
+    if (cell) {
+        pmHDU *hdu = getLowestHDU(NULL, NULL, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+            psMetadataItem *cameraItem = psMetadataLookup(cell->config, name); // The concept from the camera,
+            // or NULL
+            if (cameraItem) {
+                // Grab the concept
+                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The concept
+                // Formatted version
+                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, NULL, NULL, cell);
+                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+                psStringAppend(nameSource, "%s.SOURCE", name);
+                bool mdok = true;       // Status of MD lookup
+                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+                if (mdok && strlen(source) > 0) {
+                    if (strcasecmp(source, "HEADER") == 0) {
+                        if (cameraItem->type != PS_DATA_STRING) {
+                            psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by header, but is not "
+                                     "of type STR --- ignored.\n", conceptItem->name)
+                            continue;
+                        }
+                        writeHeader(hdu, cameraItem->data.V, formatted);
+                        continue;
+                    }
+                    if (strcasecmp(source, "VALUE") == 0) {
+                        if (! compareConcepts(cameraItem, formatted)) {
+                            psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
+                                     "format, but the values don't match.\n");
+                        }
+                        continue;
+                    }
+                    psLogMsg(__func__, PS_LOG_WARN, "Concept source %s isn't HEADER or VALUE --- can't "
+                             "write\n", nameSource);
+                    continue;
+                }
+                // Assume it's specified by value
+                if (! compareConcepts(cameraItem, formatted)) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
+                             "format, but the values don't match.\n");
+                }
+            }
+
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+
+bool pmConceptsWriteToDefault(psMetadata *specs, // The concept specifications
+                              pmFPA *fpa, // The FPA
+                              pmChip *chip, // The chip
+                              pmCell *cell, // The cell
+                              psMetadata *concepts // The concepts
+                             )
+{
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
+    if (mdok && defaults) {
+        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+            psMetadataItem *defaultItem = psMetadataLookup(defaults, name); // The item from the DEFAULTS
+            if (defaultItem) {
+                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
+                if (! compareConcepts(cameraItem, formatted)) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by the DEFAULTS in the camera "
+                             "format, but the values don't match.\n");
+                }
+            }
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+// XXX need to write multiple headers if I get a list
+bool pmConceptsWriteToHeader(psMetadata *specs, // The concept specifications
+                             pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psMetadata *concepts // The concepts
+                            )
+{
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *translation = psMetadataLookupMD(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
+    if (mdok && translation) {
+        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+            psMetadataItem *headerItem = psMetadataLookup(translation, name); // The item from the TRANSLATION
+            if (headerItem) {
+                if (headerItem->type != PS_DATA_STRING) {
+                    psLogMsg(__func__, PS_LOG_WARN, "TRANSLATION keyword for concept %s isn't of type STR ---"
+                             " ignored.", name);
+                    continue;
+                }
+                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
+                psList *keywords = psStringSplit(headerItem->data.V, " ,;"); // List of header keywords
+                if (formatted->type == PS_DATA_LIST) {
+                    psList *values = formatted->data.V; // The values for the headers
+                    if (values->n != keywords->n) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Number of headers specified does not match number "
+                                 "of values for concept %s.\n", name);
+                    }
+                    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator
+                    psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false);
+                    psMetadataItem *valuesItem = NULL; // Item from list
+                    while ((valuesItem = psListGetAndIncrement(valuesIter))) {
+                        psString keyword = psListGetAndIncrement(keywordsIter); // Keyword from the list
+                        if (strlen(keyword) > 0) {
+                            writeHeader(hdu, keyword, formatted);
+                        }
+                    }
+                    psFree(valuesIter);
+                    psFree(keywordsIter);
+                } else {
+                    psString keyword = psListGet(keywords, PS_LIST_HEAD); // The keyword
+                    writeHeader(hdu, keyword, formatted);
+                }
+            }
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+
+bool pmConceptsWriteToDatabase(psMetadata *specs, // The concept specifications
+                               pmFPA *fpa, // The FPA
+                               pmChip *chip, // The chip
+                               pmCell *cell, // The cell
+                               psDB *db,// The database handle
+                               psMetadata *concepts // The concepts
+                              )
+{
+    bool mdok = true;               // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdok, cameraFormat, "DATABASE"); // The DATABASE spec
+    if (mdok && database) {
+        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
+        psMetadata *cameraFormat = hdu->format; // The camera format
+        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *specItem = NULL;    // Item from the specs metadata
+        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+            pmConceptSpec *spec = specItem->data.V; // The specification
+            psString name = specItem->name; // The concept name
+
+            psMetadataItem *dbItem = psMetadataLookup(&mdok, database, name); // The item from the DATABASE
+            if (dbItem) {
+                if (dbItem->type != PS_DATA_METADATA) {
+                    psLogMsg(__func__, PS_LOG_WARN, "DATABASE keyword for concept %s isn't of type METADATA "
+                             "--- ignored.\n", name);
+                    continue;
+                }
+
+                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
+
+                psMetadata *dbLookup = dbItem->data.V; // How to look up the value of interest
+                // Name of the table
+                const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE");
+                // Name of "where" columns
+                const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL");
+                // Values for "where" columns
+                const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS");
+
+                // 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
+                                    psMetadataAddItem(selection, formatted, PS_LIST_TAIL, PS_META_REPLACE);
+                                    psFree(formatted);
+                                }
+                            }
+                            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.
+            }
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+
+
+
+#if 0
 
 // Well, not really "write", but check to make sure it's there and matches
@@ -368,2 +688,3 @@
 }
 
+#endif
