Index: /trunk/psModules/src/camera/pmFPAConstruct.c
===================================================================
--- /trunk/psModules/src/camera/pmFPAConstruct.c	(revision 12563)
+++ /trunk/psModules/src/camera/pmFPAConstruct.c	(revision 12564)
@@ -17,4 +17,10 @@
 #include "pmHDUUtils.h"
 
+
+#define TABLE_OF_CONTENTS "CONTENTS"    // Name for camera format metadata containing the contents
+#define CHIP_TYPES "CHIPS"              // Name for camera format metadata containing the chip types
+#define CELL_TYPES "CELLS"              // Name for camera format metadata containing the cell types
+
+
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // File-static functions
@@ -30,7 +36,7 @@
 
     bool status = true;                 // Result of MD lookup
-    psMetadata *cells = psMetadataLookupMetadata(&status, format, "CELLS"); // The CELLS
+    psMetadata *cells = psMetadataLookupMetadata(&status, format, CELL_TYPES); // The CELLS
     if (!status || !cells) {
-        psError(PS_ERR_IO, true, "Unable to determine CELLS of camera.\n");
+        psError(PS_ERR_IO, true, "Unable to find %s in camera format.\n", CELL_TYPES);
         return NULL;
     }
@@ -45,5 +51,5 @@
 
 // Parse a list of first:second:third pairs in a string
-static bool parseContent(psArray **first, // Array of the first values
+static int parseContent(psArray **first, // Array of the first values
                          psArray **second, // Array of the second values
                          psArray **third, // Array of the third values
@@ -51,46 +57,38 @@
                         )
 {
-    assert(first);
-    assert(second);
-    assert(third);
     assert(string && strlen(string) > 0);
-
-    bool allOK = true;                  // Everything was OK?
+    // Must populate 'first', 'second', 'third' in order.
+    assert(!second || first);
+    assert(!third || second);
+
+    int numArrays = third ? 3 : (second ? 2 : 1); // Number of arrays
+
     psList *values = psStringSplit(string, " ,;", true); // List of the parts
-    *first = psArrayAlloc(values->n);
-    *second = psArrayAlloc(values->n);
-    *third = psArrayAlloc(values->n);
+
+    if (first) {
+        *first = psArrayAlloc(values->n);
+    }
+    if (second) {
+        *second = psArrayAlloc(values->n);
+    }
+    if (third) {
+        *third = psArrayAlloc(values->n);
+    }
     int num = 0;
     psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
     psString value = NULL;               // "first:second:third" string
     while ((value = psListGetAndIncrement(valuesIter))) {
-        psList *firstSecondThird = psStringSplit(value, ":", true); // List containing the first, second, third
-        psArray *fst = psListToArray(firstSecondThird); // An array representation
-        psFree(firstSecondThird);
-        psString firstPart = NULL;      // The first part
-        psString secondPart = NULL;     // The second part
-        psString thirdPart = NULL;      // The third part
-        switch (firstSecondThird->n) {
-        case 1:
-            thirdPart = fst->data[0];
-            break;
-        case 2:
-            secondPart = fst->data[0];
-            thirdPart = fst->data[1];
-            break;
-        case 3:
-            firstPart = fst->data[0];
-            secondPart = fst->data[1];
-            thirdPart = fst->data[2];
+        psArray *fst = psStringSplitArray(value, ":", true); // First, second, third
+        switch (numArrays) {
+          case 3:
+            psArraySet(*third, num, fst->data[2]);
+          case 2:
+            psArraySet(*second, num, fst->data[1]);
+          case 1:
+            psArraySet(*first, num, fst->data[0]);
             break;
         default:
-            psLogMsg(__func__, PS_LOG_WARN, "Badly formated specifier: %s --- ignored.\n", value);
-            allOK = false;
-            psFree(fst);
-            continue;
-        }
-        psArraySet(*first, num, firstPart);
-        psArraySet(*second, num, secondPart);
-        psArraySet(*third, num, thirdPart);
+          psAbort("Should never get here.");
+        }
         num++;
         psFree(fst);
@@ -99,5 +97,5 @@
     psFree(values);
 
-    return allOK;
+    return num;
 }
 
@@ -268,56 +266,51 @@
 
 
-// Given a (string) list of contents "chip:cell:type chip:cell:type", put the HDU in the correct place and
-// plug in the cell configuration information
-static int processContents(pmFPA *fpa,  // The FPA
-                           pmChip *chip, // The chip, or NULL
-                           pmCell *cell, // The cell, or NULL
-                           pmHDU *hdu,  // The HDU to be added
-                           pmFPALevel level, // The level at which to add the HDU
-                           const char *contents, // The contents line, consisting of a list of chip:cell
-                           const psMetadata *format // Camera format configuration
-                          )
+// Given a list of contents, put the HDU in the correct place and plug in the cell configuration information
+static bool processContents(pmFPA *fpa,  // The FPA
+                            pmChip *chip, // The chip
+                            pmCell *cell, // The cell
+                            pmHDU *hdu,  // The HDU to be added
+                            pmFPALevel level, // The level at which to add the HDU
+                            psArray *chipNames, // The chip names
+                            psArray *cellNames, // The cell names
+                            psArray *cellTypes, // The cell types
+                            const psMetadata *format // Camera format configuration
+                            )
 {
     assert(fpa);
-    assert(contents && strlen(contents) > 0);
-    assert(!cell || (cell && chip));    // Need both chip and cell if given a cell
+    assert(cellTypes);
+    long num = cellTypes->n;            // Number of entries to add
+    assert(chip || (chipNames && chipNames->n == num));
+    assert(cell || (cellNames && cellNames->n == num));
     assert(format);
 
     if (hdu && level == PM_FPA_LEVEL_FPA) {
         if (!addHDUtoFPA(fpa, hdu)) {
-            psError(PS_ERR_UNKNOWN, false, "Adding HDU to FPA (%s)", contents);
-            return -1;
-        }
-    }
-
-    // Parse the list of chip:cell:type
-    psArray *chips = NULL;              // The chips (first bits)
-    psArray *cells = NULL;              // The cells (second bits)
-    psArray *types = NULL;              // The cell types (third bits)
-    int numCells = 0;                   // Number of cells processed
-    parseContent(&chips, &cells, &types, contents);
-    for (int i = 0; i < types->n; i++) {
-        psString chipName = chips->data[i]; // The name of the chip
-        psString cellName = cells->data[i]; // The name of the cell
-        psString cellType = types->data[i]; // The type of the cell
-
-        // Get the chip
-        pmChip *newChip = NULL;         // The chip specified
+            psError(PS_ERR_UNKNOWN, false, "Unable to add HDU to FPA");
+            return false;
+        }
+    }
+    // Load fpa-related concepts
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, false, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for fpa\n");
+        return false;
+    }
+
+    for (int i = 0; i < num; i++) {
+        psString cellType = cellTypes->data[i]; // The type of the cell
+
+        // Find the chip
+        pmChip *newChip;                // Chip of interest
         if (chip) {
             newChip = chip;
-        } else if (chipName) {
-            // Find the chip
+        } else {
+            psString chipName = chipNames->data[i]; // The name of the chip
             int chipNum = pmFPAFindChip(fpa, chipName); // The chip we're looking for
             if (chipNum == -1) {
-                psLogMsg(__func__, PS_LOG_WARN, "Unable to find chip %s in fpa --- ignored.\n", chipName);
-                continue;
+                psError(PS_ERR_LOCATION_INVALID, false,
+                        "Unable to find chip %s in fpa --- ignored.\n", chipName);
+                return false;
             }
             newChip = fpa->chips->data[chipNum];
-        }
-
-        if (!newChip) {
-            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine chip for entry %d of content (follows) --- "
-                     "ignored.\n\t%s", i, contents);
-            continue;
         }
 
@@ -326,37 +319,25 @@
             addHDUtoChip(newChip, hdu);
         }
-        // load chip-related concepts
+        // Load chip-related concepts
         if (!pmConceptsReadChip(newChip, PM_CONCEPT_SOURCE_DEFAULTS, false, false, NULL)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for chip %s\n",
-                    chipName);
-            return -1;
-        }
-
-        // Get the cell
-        pmCell *newCell = NULL;         // The cell specified
+            psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for chip\n");
+            return false;
+        }
+
+        // Find the cell
+        pmCell *newCell;                // Cell of interest
         if (cell) {
             newCell = cell;
-        } else if (cellName) {
-            // Find the cell
+        } else {
+            psString cellName = cellNames->data[i]; // The name of the cell
             int cellNum = pmChipFindCell(newChip, cellName); // The cell we're looking for
             if (cellNum == -1) {
-                psLogMsg(__func__, PS_LOG_WARN, "Unable to find cell %s in chip %s --- ignored.\n",
-                         cellName, chipName);
-                continue;
+                psError(PS_ERR_LOCATION_INVALID, false, "Unable to find cell %s in chip --- ignored.\n",
+                        cellName);
+                return false;
             }
             newCell = newChip->cells->data[cellNum];
         }
-        if (!newCell) {
-            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine cell for entry %d of content (follows) --- "
-                     "ignored.\n\t%s", i, contents);
-            continue;
-        }
-
-        // Get the type
-        if (!cellType) {
-            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine cell type for entry %d of content (follows) "
-                     "--- ignored.\n\t%s", i, contents);
-            continue;
-        }
+
         psMetadata *cellData = getCellData(format, cellType); // Data for this cell
 
@@ -367,6 +348,5 @@
         // Put in the cell data
         if (newCell->config) {
-            psLogMsg(__func__, PS_LOG_WARN, "Overwriting cell data in chip %s, cell %s\n", chipName,
-                     cellName);
+            psLogMsg(__func__, PS_LOG_WARN, "Overwriting cell data in chip\n");
             psFree(newCell->config); // Make way!
         }
@@ -374,17 +354,14 @@
         if (!pmConceptsReadCell(newCell, PM_CONCEPT_SOURCE_CELLS | PM_CONCEPT_SOURCE_DEFAULTS,
                                 false, NULL)) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for chip %s, "
-                    "cell %s, type %s", chipName, cellName, cellType);
-            return -1;
-        }
-        numCells++;
-    }
-    psFree(chips);
-    psFree(cells);
-    psFree(types);
-
-    return numCells;
-}
-
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to read concepts from camera and defaults for cell type %s", cellType);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+#if 0
 // Return the level at which EXTENSIONS go, from the FILE metadata within the camera format
 static pmFPALevel hduLevel(const psMetadata *format // The camera format configuration
@@ -418,48 +395,512 @@
     return level;
 }
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Public functions
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-pmFPA *pmFPAConstruct(const psMetadata *camera)
-{
-    PS_ASSERT_PTR_NON_NULL(camera, NULL);
-
-    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out
-
-    bool mdok = true;                   // Status from MD lookups
-    psMetadata *components = psMetadataLookupMetadata(&mdok, camera, "FPA"); // FPA components
-    if (!mdok || !components) {
-        psError(PS_ERR_IO, true, "Failed to lookup \"FPA\"");
-        psFree(fpa);
+#endif
+
+// Find the chip of interest within the FPA, using either the view (simple) or the FITS header (bit more
+// complicated).  Also updates the provided view to point to the chip if we have to find it.
+static pmChip *whichChip(pmFPAview *view, // View to chip, modified
+                         const pmFPA *fpa, // FPA holding chip of interest
+                         const pmFPAview *phuView, // View to PHU, or NULL
+                         const psMetadata *fileInfo, // FILE information from camera format
+                         const psMetadata *header // FITS header, or NULL
+                         )
+{
+    assert(view);
+    assert(fpa);
+    assert(phuView || header);
+    assert(fileInfo);
+
+    if (phuView) {
+        return pmFPAviewThisChip(phuView, fpa);
+    }
+
+    psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, header);
+    psTrace("psModules.camera", 5, "This is chip %s\n", chipName);
+    int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
+    if (chipNum == -1) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find chip %s in FPA.\n", chipName);
         return NULL;
     }
-    psMetadataIterator *componentsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL);
-    psMetadataItem *componentsItem = NULL; // Item from components
-    while ((componentsItem = psMetadataGetAndIncrement(componentsIter))) {
-        const char *chipName = componentsItem->name; // Name of the chip
-        if (componentsItem->type != PS_DATA_STRING) {
-            psLogMsg(__func__, PS_LOG_WARN, "Element %s in FPA within the camera configuration is not of "
-                     "type STR (type=%x) --- ignored.\n", chipName, componentsItem->type);
-            continue;
-        }
-
-        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
-        psList *cellNames = psStringSplit(componentsItem->data.V, " ,;", true); // List of cell names
-        psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
-
-        psString cellName = NULL;       // Name of cell
-        while ((cellName = psListGetAndIncrement(cellNamesIter))) {
-            pmCell *cell = pmCellAlloc(chip, cellName); // New cell
-            psFree(cell);               // Drop reference
-        }
-        psFree(chip);                   // Drop reference
-        psFree(cellNamesIter);
+    psFree(chipName);
+    view->chip = chipNum;
+    return fpa->chips->data[chipNum];
+}
+
+// Find the cell of interest within the chip, using either the view (simple) or the FITS header (bit more
+// complicated).  Also updates the provided view to point to the cell if we have to find it.
+static pmCell *whichCell(pmFPAview *view, // View to cell
+                         const pmChip *chip, // Chip holding cell of interest
+                         const pmFPAview *phuView, // View to PHU, or NULL
+                         const psMetadata *fileInfo, // FILE information from camera format
+                         const psMetadata *header // FITS header, or NULL
+                         )
+{
+    assert(view);
+    assert(chip);
+    assert(phuView || header);
+    assert(fileInfo);
+
+    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    if (phuView) {
+        return pmFPAviewThisCell(phuView, fpa);
+    }
+
+    psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, header);
+    psTrace("psModules.camera", 5, "This is cell %s\n", cellName);
+    int cellNum = pmChipFindCell(chip, cellName); // Cell number
+    if (cellNum == -1) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find cell %s in chip.\n", cellName);
+        return NULL;
+    }
+    view->cell = cellNum;
+    return chip->cells->data[cellNum];
+}
+
+
+// PHU=FPA and EXTENSIONS=CHIP:
+// TABLE_OF_CONTENTS(METADATA) has a list of extensions, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types, each with cellName:cellType
+static bool addSource_FPA_CHIP(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    // Iterate over all extensions
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(contentsIter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Type for %s (%x) in %s METADATA in camera format is not STR",
+                    item->name, item->type, TABLE_OF_CONTENTS);
+            psFree(contentsIter);
+            return false;
+        }
+
+        const char *extname = item->name; // Extension name
+        pmHDU *hdu = pmHDUAlloc(extname); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        // What's in the extension?  It's specified by chipName:chipType
+        // Assume that an extension contains only a single chip, instead of multiple chips
+
+        psString extContents = item->data.str; // Contents of extension
+        psArray *chipNames = NULL;
+        psArray *chipTypes = NULL;
+        if (parseContent(&chipNames, &chipTypes, NULL, extContents) != 1) {
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to parse chipName:chipType in %s of %s in camera format",
+                    extname, TABLE_OF_CONTENTS);
+        }
+
+        psString chipName = psMemIncrRefCounter(chipNames->data[0]); // Name of chip
+        psString chipType = psMemIncrRefCounter(chipTypes->data[0]); // Type of chip
+        psFree(chipNames);
+        psFree(chipTypes);
+
+        // Get the chip
+        int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
+        if (chipNum == -1) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find chip %s in FPA.\n", chipName);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+        pmChip *chip = fpa->chips->data[chipNum]; // Chip of interest
+
+        // What's in the chip?
+
+        psString chipContents = psMetadataLookupStr(NULL, chips, chipType); // Contents of the chip
+        if (!chipContents) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to find chip type %s (for extension %s) in %s of camera format",
+                    chipType, extname, CHIP_TYPES);
+            psFree(chipName);
+            psFree(chipType);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psArray *cellNames = NULL;      // Cell names
+        psArray *cellTypes = NULL;      // Cell types
+        if (parseContent(&cellNames, &cellTypes, NULL, chipContents) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse chip contents (within %s->%s in camera format; for chip %s) "
+                    "as cellName:cellType", CHIP_TYPES, chipType, chipName);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(chipName);
+            psFree(chipType);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        if (!processContents(fpa, chip, NULL, hdu, PM_FPA_LEVEL_CHIP, NULL, cellNames, cellTypes, format)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to set contents for chip %s from camera format.",
+                    chipName);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(chipName);
+            psFree(chipType);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
         psFree(cellNames);
-    }
-    psFree(componentsIter);
-
-    return fpa;
+        psFree(cellTypes);
+        psFree(chipName);
+        psFree(chipType);
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+// PHU=FPA and EXTENSIONS=CELL:
+// TABLE_OF_CONTENTS(METADATA) has a list of extensions, each with a chipName:cellName:cellType.
+static bool addSource_FPA_CELL(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    // Iterate over all extensions
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(contentsIter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Type for %s (%x) in %s METADATA in camera format is not STR",
+                    item->name, item->type, TABLE_OF_CONTENTS);
+            psFree(contentsIter);
+            return false;
+        }
+
+        const char *extname = item->name; // Extension name
+        pmHDU *hdu = pmHDUAlloc(extname); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        // What's in the extension?  It's specified by (possibly multiple) chipName:cellName:cellType
+
+        psArray *chipNames = NULL;      // Chip names
+        psArray *cellNames = NULL;      // Cell names
+        psArray *cellTypes = NULL;      // Cell types
+        if (parseContent(&chipNames, &cellNames, &cellTypes, item->data.str) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse extension contents (within %s->%s in camera format) as "
+                    "chipName:cellName:cellType", TABLE_OF_CONTENTS, extname);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        if (!processContents(fpa, NULL, NULL, hdu, PM_FPA_LEVEL_CELL, chipNames, cellNames, cellTypes,
+                             format)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to set contents from camera format.");
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+// PHU=FPA and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(STR) has a list of chipName:cellName:cellType.
+static bool addSource_FPA_NONE(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psString contents = psMetadataLookupStr(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    // What's in the file?  It's specified by (possibly multiple) chipName:cellName:cellType
+
+    psArray *chipNames = NULL;          // Chip names
+    psArray *cellNames = NULL;          // Cell names
+    psArray *cellTypes = NULL;          // Cell types
+    if (parseContent(&chipNames, &cellNames, &cellTypes, contents) == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Unable to parse contents (within %s in camera format) as chipName:cellName:cellType",
+                TABLE_OF_CONTENTS);
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    if (!processContents(fpa, NULL, NULL, NULL, PM_FPA_LEVEL_NONE, chipNames, cellNames, cellTypes,
+                         format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents from camera format.");
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(chipNames);
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    return true;
+}
+
+
+// PHU=CHIP and EXTENSIONS=CELL:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipType.
+// CHIP_TYPES(METADATA) has a list of chip types, each with extension(METADATA) with cellName:cellType
+static bool addSource_CHIP_CELL(pmChip *chip, // Chip to which to add
+                                const psMetadata *format // The camera format
+                                )
+{
+    assert(chip);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+    const char *chipType = getContent(fileInfo, contents, chip, NULL); // The chip type
+
+    // What's in the chip?
+
+    psMetadata *chipContents = psMetadataLookupMetadata(NULL, chips, chipType); // Contents of the chip
+    if (!chipContents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find chip type %s in %s of camera format",
+                chipType, CHIP_TYPES);
+        return false;
+    }
+
+    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(chipContents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *contentItem;        // Content, from iteration
+    while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
+        if (contentItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Type for %s (%x) in %s METADATA in camera format is not STR",
+                    contentItem->name, contentItem->type, TABLE_OF_CONTENTS);
+            psFree(contentsIter);
+            return false;
+        }
+
+        const char *extname = contentItem->name; // Extension name
+        pmHDU *hdu = pmHDUAlloc(extname); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        psArray *cellNames = NULL;      // Cell names
+        psArray *cellTypes = NULL;      // Cell types
+        if (parseContent(&cellNames, &cellTypes, NULL, contentItem->data.str) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse chip contents (within %s->%s in camera format) "
+                    "as cellName:cellType", CHIP_TYPES, chipType);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        if (!processContents(fpa, chip, NULL, hdu, PM_FPA_LEVEL_CELL, NULL, cellNames, cellTypes, format)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to set contents for chip type %s from camera format.",
+                    chipType);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(cellNames);
+        psFree(cellTypes);
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+// PHU=CHIP and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types, each with cellName:cellType
+static bool addSource_CHIP_NONE(pmChip *chip, // Chip to which to add
+                                const psMetadata *format // The camera format
+                                )
+{
+    assert(chip);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+    pmFPA *fpa = chip->parent;          // Parent FPA
+
+    const char *chipType = getContent(fileInfo, contents, chip, NULL); // The chip type
+
+    // What's in the chip?
+
+    psString chipContents = psMetadataLookupStr(NULL, chips, chipType); // Contents of the chip
+    if (!chipContents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find chip type %s in %s of camera format", chipType, CHIP_TYPES);
+        return false;
+    }
+
+    psArray *cellNames = NULL;      // Cell names
+    psArray *cellTypes = NULL;      // Cell types
+    if (parseContent(&cellNames, &cellTypes, NULL, chipContents) == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Unable to parse chip contents (within %s->%s in camera format) "
+                "as cellName:cellType", CHIP_TYPES, chipType);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    if (!processContents(fpa, chip, NULL, NULL, PM_FPA_LEVEL_NONE, NULL, cellNames, cellTypes, format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents for chip type %s from camera format.",
+                chipType);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    return true;
+}
+
+// PHU=CELL and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a cellName:cellType
+static bool addSource_CELL_NONE(pmCell *cell, // Cell to which to add
+                                const psMetadata *format // The camera format
+                                )
+{
+    assert(cell);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+    pmChip *chip = cell->parent;        // Parent chip
+    pmFPA *fpa = chip->parent;          // Parent FPA
+
+    const char *content = getContent(fileInfo, contents, chip, NULL); // Content of cell
+
+    psArray *cellNames = NULL;      // Cell names
+    psArray *cellTypes = NULL;      // Cell types
+    if (parseContent(&cellNames, &cellTypes, NULL, content) != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Unable to parse cell contents (%s) as cellName:cellType", content);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    if (!processContents(fpa, chip, cell, NULL, PM_FPA_LEVEL_NONE, NULL, cellNames, cellTypes, format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents for cell from camera format.");
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    return true;
 }
 
@@ -531,247 +972,200 @@
     // Now, there are a few cases:
 
-    // 1. PHU=FPA and EXTENSIONS=NONE.  This is a single CCD imager, where the entire FPA is in the PHU image.
-    // In this case, the CONTENTS is of type STR (rather than METADATA), and has the usual chip:cell:cellType.
-    //
-    // 2. PHU=CHIP or PHU=CELL, and EXTENSIONS=NONE.  This is a single chip or cell from a mosaic camera in
-    // its own file (e.g., Megacam split).  In this case, the CONTENTS is of type METADATA, and consists of a
-    // menu of choices for the contents of the file.  This is because we need to work out which chip and/or
-    // cell it comes from.
-    //
-    // 3. EXTENSIONS=CHIP or EXTENSIONS=CELL.  This is a mosaic camera, with multiple extensions.  In this
-    // case, the CONTENTS is of type METADATA, and consists of a list of contents of the file.
-    //
-    // In all of the above cases, the contents are specified by chip:cell:cellType triples.  It's just how the
-    // contents as a *whole* are collected that changes, and what that collection means.
-    //
-    // We deal with each of these cases in turn.
-
-    // Case 1: PHU=FPA and EXTENSIONS=NONE.  We need to parse the single list of chip:cell:cellType entries.
-    if (strcasecmp(phuType, "FPA") == 0 && strcasecmp(extType, "NONE") == 0) {
-        phdu->blankPHU = false;
-        const char *contents = psMetadataLookupStr(&mdok, format, "CONTENTS"); // The contents of the file
-        if (!mdok || !contents || strlen(contents) == 0) {
-            psError(PS_ERR_IO, true, "Unable to find CONTENTS in the camera format configuration.\n");
-            psFree(phdu);
-            psFree(view);
-            return NULL;
-        }
-        if (install && processContents(fpa, NULL, NULL, phdu, PM_FPA_LEVEL_FPA, contents, format) < 0) {
-            psError(PS_ERR_IO, false, "Error setting CONTENTS");
-            psFree(phdu);
-            psFree(view);
-            return NULL;
-        }
-        psFree(phdu);
-
-        if (install && header && !pmConceptsRead(fpa, NULL, NULL, PM_CONCEPT_SOURCE_PHU, NULL)) {
-            psWarning("Unable to read concepts from PHU.\n");
-        }
-
-        return view;
-    }
-
-    // In cases 2 and 3, the CONTENTS is of type METADATA, and is either a menu (if EXTENSIONS=NONE), or a
-    // list of extensions otherwise.
-    psMetadata *contents = psMetadataLookupMetadata(&mdok, format, "CONTENTS"); // The contents of the file
-    if (!mdok || !contents) {
-        if (mdok && !contents) {
-            psError(PS_ERR_IO, true, "CONTENTS metadata is NULL in the camera format configuration.");
-        } else {
-            if(psMetadataLookup(format, "CONTENTS") != NULL) {
-                psError(PS_ERR_IO, true, "CONTENTS is of wrong type in camera format configuration");
-            } else {
-                psError(PS_ERR_IO, true, "Unable to find CONTENTS in the camera format configuration.");
-            }
-        }
-        psFree(phdu);
-        psFree(view);
+    // Case    PHU     EXTENSIONS     Description
+    // ====    ===     ==========     ===========
+    // 1.      FPA     CHIP           CONTENTS(METADATA) has a list of extensions, each with a chip type.
+    //                                CHIPS(METADATA) has a list of chip types, each with cell:type
+    // 2.      FPA     CELL           CONTENTS(METADATA) has a list of extensions, each with chip:cell:type
+    //                                No need for CHIPS.
+    // 3.      FPA     NONE           CONTENTS(STRING) has a list of extensions, chip:cell:type
+    //                                No need for CHIPS
+    // 4.      CHIP    CELL           CONTENTS(METADATA) is a menu, each with a chip type
+    //                                CHIPS(METADATA) has a list of chip types(METADATA), containg a list of
+    //                                extensions.
+    // 5.      CHIP    NONE           CONTENTS(METADATA) is a menu, each with a chip type
+    //                                CHIPS(METADATA) has a list of chip types(STRING) with cell:type
+    // 6.      CELL    NONE           CONTENTS(METADATA) is a menu, each with a cell type.
+    //                                No need for CHIPS.
+
+
+    pmFPALevel phuLevel = pmFPALevelFromName(phuType); // Level for PHU
+    pmFPALevel extLevel = pmFPALevelFromName(extType); // Level for extensions
+
+    switch (phuLevel) {
+      case PM_FPA_LEVEL_FPA: {
+          // We don't have to work out where the PHU is --- there's only one FPA.
+          // 'view' already points to the FPA.
+          switch (extLevel) {
+            case PM_FPA_LEVEL_CHIP:
+              phdu->blankPHU = true;
+              if (install) {
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_CHIP(fpa, format)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_CELL:
+              if (install) {
+                  phdu->blankPHU = true;
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_CELL(fpa, format)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_NONE:
+              if (install) {
+                  phdu->blankPHU = false;
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_NONE(fpa, format)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+             return view;
+            default:
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (FPA)", extType);
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          break;
+      }
+      case PM_FPA_LEVEL_CHIP: {
+          // Which chip is our PHU?
+          pmChip *chip = whichChip(view, fpa, phuView, fileInfo, header); // Chip of interest
+          if (!chip) {
+              psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip to add.");
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          switch (extLevel) {
+            case PM_FPA_LEVEL_CELL:
+              if (install) {
+                  phdu->blankPHU = true;
+                  if (!addHDUtoChip(chip, phdu) || !addSource_CHIP_CELL(chip, format)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_NONE:
+              if (install) {
+                  phdu->blankPHU = false;
+                  if (!addHDUtoChip(chip, phdu) || !addSource_CHIP_NONE(chip, format)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            default:
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (CHIP)", extType);
+              return NULL;
+          }
+          break;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          pmChip *chip = whichChip(view, fpa, phuView, fileInfo, header); // Chip of interest
+          if (!chip) {
+              psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip to add.");
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          pmCell *cell = whichCell(view, chip, phuView, fileInfo, header); // Cell of interest
+          if (!cell) {
+              psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find cell to add.");
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          if (extLevel != PM_FPA_LEVEL_NONE) {
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (CELL)", extType);
+              return NULL;
+          }
+          if (install) {
+              phdu->blankPHU = false;
+              if (!addHDUtoCell(cell, phdu) || !addSource_CELL_NONE(cell, format)) {
+                  psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                  psFree(phdu);
+                  psFree(view);
+                  return NULL;
+              }
+          }
+          psFree(phdu);
+          return view;
+          break;
+      }
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Bad PHU level: %s", phuType);
         return NULL;
     }
 
-    // Case 2: EXTENSIONS=NONE.  We only have the PHU.  The value of CONTENT tells us a header keyword, which
-    // gives us the key to the CONTENTS menu.  Or, if we've got the view specifying the PHU, we can get the
-    // chip/cell directly from that.
-    if (strcasecmp(extType, "NONE") == 0) {
-        phdu->blankPHU = false;
-        pmChip *chip = NULL;        // The chip of interest
-        pmCell *cell = NULL;        // The cell of interest
-        pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU to be added
-
-        if (phuView) {
-            // We can get the chip/cell from the view
-            if (phuView->chip != -1 && phuView->chip < fpa->chips->n) {
-                chip = fpa->chips->data[phuView->chip];
-                level = PM_FPA_LEVEL_CHIP;
-                if (phuView->cell != -1) {
-                    if (phuView->cell < chip->cells->n) {
-                        cell = chip->cells->data[phuView->cell];
-                        level = PM_FPA_LEVEL_CELL;
-                    } else {
-                        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PHU view for cell (%d) does not "
-                                "correspond to camera format.\n", phuView->cell);
-                        psFree(phdu);
-                        return NULL;
-                    }
-                }
-            } else {
-                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PHU view for chip (%d) does not correspond to "
-                        "camera format.\n", phuView->chip);
-                psFree(phdu);
-                return NULL;
-            }
-        } else {
-            // Need to look up what chip we have.
-            psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, header);
-            psTrace("psModules.camera", 5, "This is chip %s\n", chipName);
-            int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
-            if (chipNum == -1) {
-                psError(PS_ERR_IO, true, "Unable to find chip %s in FPA.\n", chipName);
-                psFree(phdu);
-                psFree(view);
-                return NULL;
-            }
-            chip = fpa->chips->data[chipNum]; // Chip of interest
-            view->chip = chipNum;
-
-            if (strcasecmp(phuType, "CHIP") == 0) {
-                level = PM_FPA_LEVEL_CHIP;
-            } else if (strcasecmp(phuType, "CELL") == 0) {
-                level = PM_FPA_LEVEL_CELL;
-                // Need to look up what cell we have.
-                psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, header);
-                int cellNum = pmChipFindCell(chip, cellName); // Cell number
-                if (cellNum == -1) {
-                    psError(PS_ERR_IO, true, "Unable to find cell %s in chip %s.\n", cellName, chipName);
-                    psFree(view);
-                    psFree(phdu);
-                    return NULL;
-                }
-                cell = chip->cells->data[cellNum];
-                view->cell = cellNum;
-                psFree(cellName);
-            } else {
-                // We have already dealt with the case PHU=FPA, in a special case above, so if we get here
-                // it's an error.
-                psError(PS_ERR_IO, true, "PHU is not FPA, CHIP or CELL.\n");
-                psFree(phdu);
-                psFree(view);
-                return NULL;
-            }
-            psFree(chipName);
-        }
-
-        if (install) {
-            const char *content = getContent(fileInfo, contents, chip, cell); // chip:cell:cellType triples
-            if (!content || strlen(content) == 0) {
-                psError(PS_ERR_IO, false, "Unable to get CONTENTS.\n");
-                psFree(phdu);
-                psFree(view);
-                return NULL;
-            }
-            if (processContents(fpa, chip, cell, phdu, level, content, format) < 0) {
-                psError(PS_ERR_IO, false, "Error setting CONTENTS");
-                psFree(phdu);
-                psFree(view);
-                return NULL;
-            }
-        }
-        psFree(phdu);
-
-        if (install && header && !pmConceptsRead(fpa, chip, cell, PM_CONCEPT_SOURCE_PHU, NULL)) {
-            psWarning("Unable to read concepts from PHU.\n");
-        }
-        return view;
-    }
-
-    // Case 3: EXTENSIONS=CHIP or EXTENSIONS=CELL.  We have extensions that we iterate through.  The CONTENTS
-    // is a list of extensions.
-    phdu->blankPHU = true;
-    pmChip *chip = NULL;                // The chip of interest
-    pmCell *cell = NULL;                // The cell of interest
-
-    // First, put in the PHU
-    if (strcasecmp(phuType, "FPA") == 0) {
-        addHDUtoFPA(fpa, phdu);
-    } else {
-        // Get the chip
-        psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, header); // Name of the chip
-        int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
-        if (chipNum == -1) {
-            psError(PS_ERR_IO, true, "Unable to find chip %s in FPA.\n", chipName);
-            psFree(view);
-            return NULL;
-        }
-        chip = fpa->chips->data[chipNum]; // The specified chip
-        view->chip = chipNum;
-
-        if (strcasecmp(phuType, "CHIP") == 0) {
-            addHDUtoChip(chip, phdu);
-        } else if (strcasecmp(phuType, "CELL") == 0) {
-            psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, header); // Name of the cell
-            int cellNum = pmChipFindCell(chip, cellName); // Cell number
-            if (cellNum == -1) {
-                psError(PS_ERR_IO, true, "Unable to find cell %s in chip %s.\n", cellName, chipName);
-                psFree(view);
-                return NULL;
-            }
-            cell = chip->cells->data[cellNum]; // The specified cell
-            view->cell = cellNum;
-            psFree(cellName);
-
-            addHDUtoCell(cell, phdu);
-        } else {
-            psError(PS_ERR_IO, true, "The format of the PHU (%s) is not FPA, CHIP or CELL.\n", phuType);
-            psFree(view);
-            return NULL;
-        }
-        psFree(chipName);
-    }
-    psFree(phdu);
-
-    pmFPALevel level = hduLevel(format);// The level at which to plug in HDU
-
-    // Now go through the contents
-    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
-    psMetadataItem *contentsItem = NULL; // Item from contents
-    while ((contentsItem = psMetadataGetAndIncrement(contentsIter))) {
-        const char *extName = contentsItem->name;
-        if (contentsItem->type != PS_DATA_STRING) {
-            psLogMsg(__func__, PS_LOG_WARN, "CONTENTS item %s is not of type STR --- ignored.\n", extName);
+    return NULL;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmFPA *pmFPAConstruct(const psMetadata *camera)
+{
+    PS_ASSERT_PTR_NON_NULL(camera, NULL);
+
+    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out
+
+    bool mdok = true;                   // Status from MD lookups
+    psMetadata *components = psMetadataLookupMetadata(&mdok, camera, "FPA"); // FPA components
+    if (!mdok || !components) {
+        psError(PS_ERR_IO, true, "Failed to lookup \"FPA\"");
+        psFree(fpa);
+        return NULL;
+    }
+    psMetadataIterator *componentsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL);
+    psMetadataItem *componentsItem = NULL; // Item from components
+    while ((componentsItem = psMetadataGetAndIncrement(componentsIter))) {
+        const char *chipName = componentsItem->name; // Name of the chip
+        if (componentsItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "Element %s in FPA within the camera configuration is not of "
+                     "type STR (type=%x) --- ignored.\n", chipName, componentsItem->type);
             continue;
         }
 
-        if (install) {
-            pmHDU *hdu = pmHDUAlloc(extName); // The extension
-            // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
-            // pointer target type"
-            hdu->format = psMemIncrRefCounter((const psPtr)format);
-
-            if (processContents(fpa, chip, cell, hdu, level, contentsItem->data.V, format) < 0) {
-                psError(PS_ERR_IO, false, "Error setting CONTENTS");
-                psFree(view);
-                psFree(hdu);
-                psFree(contentsIter);
-                return NULL;
-            }
-            psFree(hdu);
-        }
-    }
-    psFree(contentsIter);
-
-    if (install) {
-        if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, true, NULL)) {
-            psWarning("Unable to read concepts from defaults for FPA.  Attempting to "
-                      "proceed anyway.\n");
-        }
-
-        if (header && !pmConceptsRead(fpa, chip, cell, PM_CONCEPT_SOURCE_PHU, NULL)) {
-            psWarning("Unable to read concepts from PHU.\n");
-        }
-    }
-
-    return view;
-}
-
+        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+        psList *cellNames = psStringSplit(componentsItem->data.V, " ,;", true); // List of cell names
+        psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
+
+        psString cellName = NULL;       // Name of cell
+        while ((cellName = psListGetAndIncrement(cellNamesIter))) {
+            pmCell *cell = pmCellAlloc(chip, cellName); // New cell
+            psFree(cell);               // Drop reference
+        }
+        psFree(chip);                   // Drop reference
+        psFree(cellNamesIter);
+        psFree(cellNames);
+    }
+    psFree(componentsIter);
+
+    return fpa;
+}
 
 bool pmFPAAddSourceFromView(pmFPA *fpa, const char *fpaname, const pmFPAview *phuView,
Index: /trunk/psModules/src/camera/pmFPACopy.c
===================================================================
--- /trunk/psModules/src/camera/pmFPACopy.c	(revision 12563)
+++ /trunk/psModules/src/camera/pmFPACopy.c	(revision 12564)
@@ -303,9 +303,8 @@
     pmHDU *targetHDU = pmHDUFromCell(target); // The target HDU
     if (targetHDU) {
-        if (!targetHDU->header) {
-            targetHDU->header = psMetadataAlloc();
-        }
         pmHDU *sourceHDU = pmHDUFromCell(source); // The source HDU
-        if (sourceHDU->header) {
+        if (!sourceHDU) {
+            psWarning("Unable to copy header: no source header.");
+        } else if (sourceHDU->header) {
             targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
         }
@@ -315,7 +314,4 @@
     pmHDU *targetPHU = findBlankPHU(target); // The target PHU
     if (targetPHU && targetPHU != targetHDU) {
-        if (!targetPHU->header) {
-            targetPHU->header = psMetadataAlloc();
-        }
 //        pmHDU *sourcePHU = pmHDUGetHighest(source->parent->parent, source->parent, source); // A source HDU
         pmHDU *sourcePHU = findBlankPHU(source); // The target PHU
Index: /trunk/psModules/src/camera/pmFPALevel.c
===================================================================
--- /trunk/psModules/src/camera/pmFPALevel.c	(revision 12563)
+++ /trunk/psModules/src/camera/pmFPALevel.c	(revision 12564)
@@ -17,5 +17,4 @@
 const char *pmFPALevelToName(pmFPALevel level)
 {
-
     switch (level) {
     case PM_FPA_LEVEL_NONE:
@@ -49,5 +48,8 @@
     } else if (!strcasecmp(name, "READOUT")) {
         val = PM_FPA_LEVEL_READOUT;
+    } else if (!strcasecmp(name, "NONE")) {
+        val = PM_FPA_LEVEL_NONE;
     } else {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unrecognised FPA level name: %s", name);
         val = PM_FPA_LEVEL_NONE;
     }
Index: /trunk/psModules/src/camera/pmFPARead.c
===================================================================
--- /trunk/psModules/src/camera/pmFPARead.c	(revision 12563)
+++ /trunk/psModules/src/camera/pmFPARead.c	(revision 12564)
@@ -39,5 +39,5 @@
                          const psRegion *trimsec, // Trim section
                          const psList *biassecs, // Bias sections
-			 fpaReadType type
+                         fpaReadType type
                         )
 {
@@ -65,25 +65,25 @@
     switch (type) {
       case FPA_READ_TYPE_IMAGE:
-	if (readout->image) {
-	    psFree (readout->image);
-	}
-	readout->image = psMemIncrRefCounter(psImageSubset(image, region));	
-	break;
+        if (readout->image) {
+            psFree (readout->image);
+        }
+        readout->image = psMemIncrRefCounter(psImageSubset(image, region));
+        break;
       case FPA_READ_TYPE_MASK:
-	if (readout->mask) {
-	    psFree (readout->mask);
-	}
-	readout->mask = psMemIncrRefCounter(psImageSubset(image, region));	
-	break;
+        if (readout->mask) {
+            psFree (readout->mask);
+        }
+        readout->mask = psMemIncrRefCounter(psImageSubset(image, region));
+        break;
       case FPA_READ_TYPE_WEIGHT:
-	if (readout->weight) {
-	    psFree (readout->weight);
-	}
-	readout->weight = psMemIncrRefCounter(psImageSubset(image, region));	
-	break;
+        if (readout->weight) {
+            psFree (readout->weight);
+        }
+        readout->weight = psMemIncrRefCounter(psImageSubset(image, region));
+        break;
       default:
-	psAbort("Unknown read type: %x\n", type);
-    }
-    
+        psAbort("Unknown read type: %x\n", type);
+    }
+
     // Get the list of overscans
     // XXX should this step only be performed for IMAGE, not MASK and WEIGHT types?
@@ -210,5 +210,5 @@
     // check if we have read the desired data, read it if needed
     bool (*hduReadFunc)(pmHDU*, psFits*) = NULL; // Function to use to read the HDU
-    void *dataPointer = NULL; 		// pointer to location of desired data
+    void *dataPointer = NULL;           // pointer to location of desired data
     switch (type) {
       case FPA_READ_TYPE_IMAGE:
@@ -234,9 +234,9 @@
     // do we have the data we want (image, header, or etc).
     if (!dataPointer) {
-	// attempt to read in the desired data
-	if (!hduReadFunc(hdu, fits)) {
-	    psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
-	    return false;
-	}
+        // attempt to read in the desired data
+        if (!hduReadFunc(hdu, fits)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
+            return false;
+        }
     }
 
@@ -250,10 +250,10 @@
     // skip the image arrays completely for the header-only files
     if (type == FPA_READ_TYPE_HEADER) {
-	pmCellSetDataStatus(cell, true);
-	return true;
+        pmCellSetDataStatus(cell, true);
+        return true;
     }
 
     // set up pointers for the different possible image arrays
-    psArray *imageArray = NULL;	// Array of images in the HDU
+    psArray *imageArray = NULL; // Array of images in the HDU
     psElemType imageType = PS_TYPE_F32; // Expected type for image
     switch (type) {
@@ -263,5 +263,5 @@
         break;
       case FPA_READ_TYPE_MASK:
-	imageArray = hdu->masks;
+        imageArray = hdu->masks;
         imageType = PS_TYPE_MASK;
         break;
Index: /trunk/psModules/src/concepts/pmConceptsWrite.c
===================================================================
--- /trunk/psModules/src/concepts/pmConceptsWrite.c	(revision 12563)
+++ /trunk/psModules/src/concepts/pmConceptsWrite.c	(revision 12564)
@@ -57,5 +57,5 @@
         while ((cItem = psListGetAndIncrement(cIter))) {
             if (cItem->type != PS_DATA_STRING) {
-                psLogMsg(__func__, PS_LOG_WARN, "psMetadataItem from list is of type %x instead of "
+                psWarning("psMetadataItem from list is of type %x instead of "
                          "%x (PS_DATA_STRING) --- can't interpret.\n", cItem->type, PS_DATA_STRING);
                 psFree(cIter);
@@ -178,5 +178,5 @@
         }
     default:
-        psLogMsg(__func__, PS_LOG_WARN, "Type of %s is not suitable for a FITS header --- not added.\n",
+        psWarning("Type of %s is not suitable for a FITS header --- not added.\n",
                  item->name);
         return false;
@@ -261,5 +261,5 @@
                 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 "
+                        psWarning("Concept %s is specified by header, but is not "
                                  "of type STR --- ignored.\n", conceptItem->name);
                         continue;
@@ -286,10 +286,10 @@
                     psTrace("psModules.concepts", 8, "Checking %s against camera format.\n", name);
                     if (! compareConcepts(formatted, cameraItem)) {
-                        psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
+                        psWarning("Concept %s is specified by value in the camera "
                                  "format, but the values don't match.\n", name);
                     }
                     psFree(formatted);
                 } else {
-                    psLogMsg(__func__, PS_LOG_WARN, "Concept source %s isn't HEADER or VALUE --- can't "
+                    psWarning("Concept source %s isn't HEADER or VALUE --- can't "
                              "write\n", nameSource);
                 }
@@ -303,5 +303,5 @@
 
                 if (! compareConcepts(formatted, cameraItem)) {
-                    psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
+                    psWarning("Concept %s is specified by value in the camera "
                              "format, but the values don't match.\n", name);
                 }
@@ -347,5 +347,5 @@
                 psString dependKey = psMetadataLookupStr(&mdok, defaults, dependName); // The keyword
                 if (!mdok || !dependKey || strlen(dependKey) == 0) {
-                    psLogMsg(__func__, PS_LOG_WARN, "Can't find %s in the DEFAULTS for %s --- ignored.\n",
+                    psWarning("Can't find %s in the DEFAULTS for %s --- ignored.\n",
                              dependName, name);
                     psFree(dependName);
@@ -354,5 +354,5 @@
                 psString dependValue = psMetadataLookupStr(&mdok, concepts, dependKey); // The value
                 if (!mdok || !dependValue || strlen(dependValue) == 0) {
-                    psLogMsg(__func__, PS_LOG_WARN, "Concept %s specified by %s isn't of type STR -- "
+                    psWarning("Concept %s specified by %s isn't of type STR -- "
                              "ignored.\n", dependKey, dependName);
                     psFree(dependName);
@@ -360,11 +360,11 @@
                 }
                 // Get the actual item of interest, and correct the name to match the concept name
-		defaultItem = psMetadataLookup(defaultItem->data.md, dependValue);
-		if (!defaultItem) {
-                    psLogMsg(__func__, PS_LOG_WARN, "Concept dependency name %s is not found "
-			     "in concept table for %s -- ignored.\n", dependValue, dependName);
+                defaultItem = psMetadataLookup(defaultItem->data.md, dependValue);
+                if (!defaultItem) {
+                    psWarning("Concept dependency name %s is not found "
+                             "in concept table for %s -- ignored.\n", dependValue, dependName);
                     psFree(dependName);
                     continue;
-		}
+                }
                 defaultItem = psMetadataItemCopy(defaultItem);
                 psFree(dependName);
@@ -381,5 +381,5 @@
             }
             if (! compareConcepts(formatted, defaultItem)) {
-                psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by the DEFAULTS in the camera "
+                psWarning("Concept %s is specified by the DEFAULTS in the camera "
                          "format, but the values don't match.\n", name);
             }
@@ -418,5 +418,5 @@
         if (headerItem) {
             if (headerItem->type != PS_DATA_STRING) {
-                psLogMsg(__func__, PS_LOG_WARN, "TRANSLATION keyword for concept %s isn't of type STR ---"
+                psWarning("TRANSLATION keyword for concept %s isn't of type STR ---"
                          " ignored.", name);
                 continue;
@@ -471,5 +471,5 @@
         if (dbItem) {
             if (dbItem->type != PS_DATA_METADATA) {
-                psLogMsg(__func__, PS_LOG_WARN, "DATABASE keyword for concept %s isn't of type METADATA "
+                psWarning("DATABASE keyword for concept %s isn't of type METADATA "
                          "--- ignored.\n", name);
                 continue;
@@ -508,5 +508,5 @@
                         char *dependName = 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 "
+                            psWarning("One of the columns or value names for %s is "
                                      " empty --- ignored.\n", name);
                         } else {
@@ -543,10 +543,10 @@
                     // 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 --- "
+                        psWarning("Unable to find any rows in DB for %s --- "
                                  "ignored\n", name);
                         return false;
                     } else {
                         if (dbResult->n > 1) {
-                            psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s "
+                            psWarning("Multiple rows returned in DB lookup for %s "
                                      "--- ignored.\n", name);
                         }
Index: /trunk/psModules/src/config/pmConfig.c
===================================================================
--- /trunk/psModules/src/config/pmConfig.c	(revision 12563)
+++ /trunk/psModules/src/config/pmConfig.c	(revision 12564)
@@ -4,6 +4,6 @@
  *  @author EAM (IfA)
  *
- *  @version $Revision: 1.80 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2007-03-22 18:07:18 $
+ *  @version $Revision: 1.81 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-03-23 03:09:53 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -129,4 +129,6 @@
 void pmConfigSet(const char *path)
 {
+    PS_ASSERT_STRING_NON_EMPTY(path,);
+
     pmConfigDone();
 
Index: /trunk/psModules/src/config/pmConfigCamera.c
===================================================================
--- /trunk/psModules/src/config/pmConfigCamera.c	(revision 12563)
+++ /trunk/psModules/src/config/pmConfigCamera.c	(revision 12564)
@@ -11,4 +11,9 @@
 
 #include "pmConfigCamera.h"
+
+
+#define TABLE_OF_CONTENTS "CONTENTS"    // Name for camera format metadata containing the contents
+#define CHIP_TYPES "CHIPS"              // Name for camera format metadata containing the chip types
+#define CELL_TYPES "CELLS"              // Name for camera format metadata containing the cell types
 
 
@@ -68,5 +73,5 @@
                              psMetadata *newCameras, // New list of camera configurations
                              const char *name, // Name of original camera configuration
-                             pmFPALevel level // Level
+                             pmFPALevel mosaicLevel // Level to which we are mosaicking
                             )
 {
@@ -74,5 +79,5 @@
     assert(newCameras);
     assert(name);
-    assert(level == PM_FPA_LEVEL_CHIP || level == PM_FPA_LEVEL_FPA);
+    assert(mosaicLevel == PM_FPA_LEVEL_CHIP || mosaicLevel == PM_FPA_LEVEL_FPA);
 
     // See if the old one is there
@@ -84,5 +89,5 @@
     // See if the new one is already there
     psString newName = NULL;       // Name of mosaicked camera
-    psStringAppend(&newName, "_%s-%s", name, level == PM_FPA_LEVEL_CHIP ? "CHIP" : "FPA");
+    psStringAppend(&newName, "_%s-%s", name, mosaicLevel == PM_FPA_LEVEL_CHIP ? "CHIP" : "FPA");
     if (psMetadataLookup(oldCameras, newName)) {
         return true;
@@ -99,34 +104,35 @@
         return NULL;
     }
-    switch (level) {
-    case PM_FPA_LEVEL_CHIP: {
-            // Replace the contents of each chip with a single cell
-            psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
-            psMetadataItem *fpaItem = NULL;     // Item from iteration
-            while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
-                if (fpaItem->type != PS_DATA_STRING) {
-                    psWarning("Element %s within FPA in camera configuration is not of type STR.",
-                              fpaItem->name);
-                    continue;
-                }
-
-                psFree(fpaItem->data.str);
-                fpaItem->data.str = psStringCopy("MosaickedCell");
-                psFree(fpaItem->comment);
-                fpaItem->comment = psStringCopy("Mosaicked cell; automatically generated");
-            }
-            psFree(fpaIter);
-        }
-        break;
-    case PM_FPA_LEVEL_FPA: {
-            while (psListLength(fpa->list) > 0) {
-                psMetadataRemoveIndex(fpa, PS_LIST_TAIL);
-            }
-
-            psMetadataAddStr(fpa, PS_LIST_HEAD, "MosaickedChip", 0,
-                             "Mosaicked chip with mosaicked cell; automatically generated",
-                             "MosaickedCell");
-        }
-        break;
+    switch (mosaicLevel) {
+      case PM_FPA_LEVEL_CHIP: {
+          // Replace the contents of each chip with a single cell
+          psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
+          psMetadataItem *fpaItem = NULL;     // Item from iteration
+          while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
+              if (fpaItem->type != PS_DATA_STRING) {
+                  psWarning("Element %s within FPA in camera configuration is not of type STR.",
+                            fpaItem->name);
+                  continue;
+              }
+
+              psFree(fpaItem->data.str);
+              fpaItem->data.str = psStringCopy("MosaickedCell");
+              psFree(fpaItem->comment);
+              fpaItem->comment = psStringCopy("Mosaicked cell; automatically generated");
+          }
+          psFree(fpaIter);
+          break;
+      }
+      case PM_FPA_LEVEL_FPA: {
+          // Replace the contents of the FPA with a single chip containing a single cell
+          while (psListLength(fpa->list) > 0) {
+              psMetadataRemoveIndex(fpa, PS_LIST_TAIL);
+          }
+
+          psMetadataAddStr(fpa, PS_LIST_HEAD, "MosaickedChip", 0,
+                           "Mosaicked chip with mosaicked cell; automatically generated",
+                           "MosaickedCell");
+          break;
+      }
     default:
         psAbort("Should never get here.\n");
@@ -142,5 +148,5 @@
         psMetadata *format = formatsItem->data.V; // The camera format
 
-        // Add a RULE
+        // Add a RULE, so that when a mosaic is written to a FITS file, it can be recognised again when read.
         psMetadata *rule = psMetadataLookupMetadata(&mdok, format, "RULE"); // Way to identify format from PHU
         if (!mdok || !rule) {
@@ -148,5 +154,5 @@
             continue;
         }
-        switch (level) {
+        switch (mosaicLevel) {
         case PM_FPA_LEVEL_CHIP:
             psMetadataAddStr(rule, PS_LIST_TAIL, "PSMOSAIC", 0, "Mosaicked level", "CHIP");
@@ -173,7 +179,7 @@
         }
         if (strcasecmp(phuItem->data.str, "CELL") == 0 ||
-                (level == PM_FPA_LEVEL_FPA && (strcasecmp(phuItem->data.str, "CHIP") == 0))) {
+                (mosaicLevel == PM_FPA_LEVEL_FPA && (strcasecmp(phuItem->data.str, "CHIP") == 0))) {
             psFree(phuItem->data.str);
-            if (level == PM_FPA_LEVEL_CHIP) {
+            if (mosaicLevel == PM_FPA_LEVEL_CHIP) {
                 phuItem->data.str = psStringCopy("CHIP");
             } else {
@@ -187,26 +193,44 @@
             continue;
         }
-        switch (level) {
-        case PM_FPA_LEVEL_CHIP:
-            if (strcasecmp(extensionsItem->data.str, "NONE") == 0) {
-                if (strcasecmp(phuItem->data.str, "FPA") == 0) {
-                    break;              // Don't need a "CONTENT" to identify the content!
-                }
-                psMetadataItem *contentItem = psMetadataLookup(file, "CONTENT"); // Key to CONTENTS menu
-                if (!contentItem || contentItem->type != PS_DATA_STRING) {
-                    psWarning("Couldn't find CONTENT of type STR in the FILE information "
-                              "in the camera format %s.\n", formatsItem->name);
-                    continue;
-                }
-                psFree(contentItem->data.str);
-                contentItem->data.str = psStringCopy("{CHIP.NAME}");
-            } else if (strcasecmp(extensionsItem->data.str, "CELL") == 0) {
+        switch (mosaicLevel) {
+          case PM_FPA_LEVEL_CHIP:
+            if (strcasecmp(phuItem->data.str, "FPA") == 0 &&
+                strcasecmp(extensionsItem->data.str, "CELL") == 0) {
                 psFree(extensionsItem->data.str);
                 extensionsItem->data.str = psStringCopy("CHIP");
-            }
-            break;
-        case PM_FPA_LEVEL_FPA:
+            } else {
+                psFree(extensionsItem->data.str);
+                extensionsItem->data.str = psStringCopy("NONE");
+
+                if (strcasecmp(phuItem->data.str, "FPA") == 0) {
+                    // Don't need a "CONTENT" to identify the content!
+                    if (psMetadataLookup(file, "CONTENT")) {
+                        psMetadataRemoveKey(file, "CONTENT");
+                    }
+                    break;
+                }
+                psMetadataAddStr(file, PS_LIST_TAIL, "CONTENT", PS_META_REPLACE, "Key to CONTENTS menu",
+                                 "{CHIP.NAME}");
+            }
+
+#if 0
+            // Don't need CELL.NAME for chip-mosaicked camera
+            if (psMetadataLookup(file, "CELL.NAME")) {
+                psMetadataRemoveKey(file, "CELL.NAME");
+            }
+#endif
+            break;
+          case PM_FPA_LEVEL_FPA:
             psFree(extensionsItem->data.str);
             extensionsItem->data.str = psStringCopy("NONE");
+#if 0
+            // Don't need CHIP.NAME or CELL.NAME for fpa-mosaicked camera
+            if (psMetadataLookup(file, "CHIP.NAME")) {
+                psMetadataRemoveKey(file, "CHIP.NAME");
+            }
+            if (psMetadataLookup(file, "CELL.NAME")) {
+                psMetadataRemoveKey(file, "CELL.NAME");
+            }
+#endif
             break;
         default:
@@ -215,28 +239,66 @@
 
         // Fix up the CONTENTS to contain only the mosaicked cell for each chip
-        switch (level) {
-        case PM_FPA_LEVEL_CHIP:
-            if (strcasecmp(phuItem->data.str, "FPA") == 0 &&
-                    strcasecmp(extensionsItem->data.str, "NONE") == 0) {
-                // List the contents on a single line
-                psString contentsLine = NULL; // Contents of the PHU
-                psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
-                psMetadataItem *fpaItem;    // Item from iteration
-                while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
-                    if (fpaItem->type != PS_DATA_STRING) {
-                        // We've already thrown a warning on this, above.
-                        continue;
+        switch (mosaicLevel) {
+          case PM_FPA_LEVEL_FPA:
+            psMetadataAddStr(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE, NULL,
+                             "MosaickedChip:MosaickedCell:_mosaic");
+            break;
+          case PM_FPA_LEVEL_CHIP:
+            if (strcasecmp(phuItem->data.str, "FPA") == 0) {
+                if (strcasecmp(extensionsItem->data.str, "CHIP") == 0) {
+                    // List the chipName:chipType for each chip.
+
+                    psMetadata *contents = psMetadataAlloc(); // List of contents, with chipName:chipType
+
+                    psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iteratr
+                    psMetadataItem *fpaItem;    // Item from iteration
+                    while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
+                        if (fpaItem->type != PS_DATA_STRING) {
+                            // We've already thrown a warning on this, above.
+                            continue;
+                        }
+                        psString content = NULL; // Content to add
+                        psStringAppend(&content, "%s:_mosaicChip ", fpaItem->name);
+                        psMetadataAddStr(contents, PS_LIST_TAIL, fpaItem->name, 0, NULL, content);
+                        psFree(content);
                     }
-                    psStringAppend(&contentsLine, "%s:MosaickedCell:_mosaic ", fpaItem->name);
+                    psFree(fpaIter);
+                    psMetadataAddMetadata(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE,
+                                          "List of contents", contents);
+                    psFree(contents);
+
+                    psMetadata *chips = psMetadataAlloc(); // List of chip types, with cellName:cellType
+                    psMetadataAddStr(chips, PS_LIST_TAIL, "_mosaicChip", 0, NULL,
+                                     "MosaickedCell:_mosaic");
+                    psMetadataAddMetadata(format, PS_LIST_TAIL, CHIP_TYPES, PS_META_REPLACE,
+                                          "List of chip types", chips);
+                    psFree(chips);
+                    break;
+                } else if (strcasecmp(extensionsItem->data.str, "NONE") == 0) {
+                    // List the contents on a single line
+                    psString contentsLine = NULL; // Contents of the PHU
+                    psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iteratr
+                    psMetadataItem *fpaItem;    // Item from iteration
+                    while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
+                        if (fpaItem->type != PS_DATA_STRING) {
+                            // We've already thrown a warning on this, above.
+                            continue;
+                        }
+                        psStringAppend(&contentsLine, "%s:MosaickedCell:_mosaic ", fpaItem->name);
+                    }
+                    psFree(fpaIter);
+                    psMetadataAddStr(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE,
+                                     NULL, contentsLine);
+                    psFree(contentsLine);
+                    break;
                 }
-                psFree(fpaIter);
-                psMetadataAddStr(format, PS_LIST_TAIL, "CONTENTS", PS_META_REPLACE, NULL, contentsLine);
-                psFree(contentsLine);
-                break;
-            }
-
-            psMetadata *contents = psMetadataLookupMetadata(&mdok, format, "CONTENTS"); // Contents of file
+            }
+            // PHU level is CHIP, EXTENSIONS is NONE.
+
+            psMetadata *contents = psMetadataLookupMetadata(&mdok, format,
+                                                            TABLE_OF_CONTENTS); // File contents
             if (!mdok || !contents) {
-                psWarning("Couldn't find CONTENTS in the camera format %s.\n", formatsItem->name);
+                psWarning("Couldn't find %s in the camera format %s.\n", TABLE_OF_CONTENTS,
+                          formatsItem->name);
                 continue;
             }
@@ -244,4 +306,5 @@
                 psMetadataRemoveIndex(contents, PS_LIST_TAIL);
             }
+
             psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
             psMetadataItem *fpaItem;    // Item from iteration
@@ -252,14 +315,14 @@
                 }
 
-                psString content = NULL;    // Content of the chip
-                psStringAppend(&content, "%s:MosaickedCell:_mosaic", fpaItem->name);
-                psMetadataAddStr(contents, PS_LIST_TAIL, fpaItem->name, 0, NULL, content);
-                psFree(content);            // Drop reference
+                psMetadataAddStr(contents, PS_LIST_TAIL, fpaItem->name, 0, NULL, "_mosaicChip");
             }
             psFree(fpaIter);
-            break;
-        case PM_FPA_LEVEL_FPA:
-            psMetadataAddStr(format, PS_LIST_TAIL, "CONTENTS", PS_META_REPLACE, NULL,
-                             "MosaickedChip:MosaickedCell:_mosaic");
+
+            psMetadata *chips = psMetadataAlloc(); // List of chip types, with cellName:cellType
+            psMetadataAddStr(chips, PS_LIST_TAIL, "_mosaicChip", 0, NULL,
+                             "MosaickedCell:_mosaic");
+            psMetadataAddMetadata(format, PS_LIST_TAIL, CHIP_TYPES, PS_META_REPLACE,
+                                  "List of chip types", chips);
+            psFree(chips);
             break;
         default:
@@ -268,5 +331,5 @@
 
         // Fix the cell type
-        psMetadata *cells = psMetadataLookupMetadata(&mdok, format, "CELLS"); // CELLS information
+        psMetadata *cells = psMetadataLookupMetadata(&mdok, format, CELL_TYPES); // CELLS information
         if (!mdok || !cells) {
             psWarning("Couldn't find CELLS of type METADATA in the camera format %s.\n", formatsItem->name);
@@ -316,5 +379,5 @@
         }
 
-        if (level == PM_FPA_LEVEL_FPA) {
+        if (mosaicLevel == PM_FPA_LEVEL_FPA) {
             removeChipConceptsSources(translation);
             removeChipConceptsSources(database);
@@ -347,5 +410,4 @@
 }
 
-
 // Generate the chip mosaicked version of a camera configuration
 bool pmConfigCameraMosaickedVersions(psMetadata *site, // The site configuration
