Index: trunk/psModules/src/camera/pmFPAConstruct.c
===================================================================
--- trunk/psModules/src/camera/pmFPAConstruct.c	(revision 7526)
+++ trunk/psModules/src/camera/pmFPAConstruct.c	(revision 7589)
@@ -200,44 +200,58 @@
 
 
-// Looks up the particular content based on the header
-static const char *getContent(psMetadata *fileInfo, // The FILE from the camera format configuration
-                              psMetadata *contents, // The CONTENTS from the camera format configuration
-                              psMetadata *header // The (primary) header
+// Looks up the particular content, based on the chip and cell
+static const char *getContent(const psMetadata *fileInfo, // The FILE from the camera format configuration
+                              const psMetadata *contents, // The CONTENTS from the camera format configuration
+                              const pmChip *chip, // The chip of interest
+                              const pmCell *cell // The cell of interest
                              )
 {
     assert(fileInfo);
     assert(contents);
-    assert(header);
+    assert(chip);
 
     bool mdok = true;                   // Status of MD lookup
-    const char *contentHeaders = psMetadataLookupStr(&mdok, fileInfo, "CONTENT"); // Headers for content
-    if (!mdok || !contentHeaders || strlen(contentHeaders) == 0) {
+    char *contentKey = psMetadataLookupStr(&mdok, fileInfo, "CONTENT"); // Key for CONTENTS
+    if (!mdok || !contentKey || strlen(contentKey) == 0) {
         psError(PS_ERR_IO, true, "Unable to find CONTENT in FILE within camera format configuration.\n");
         return NULL;
     }
 
-    psList *keywords = psStringSplit(contentHeaders, " ,;", true); // List of keywords
-    psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false); // Iterator
-    psString keyword = NULL;        // Keyword, from iteration
-    psString contentsKey = NULL;    // Key to the CONTENTS menu
-    bool first = true;              // Is it the first value?
-    while ((keyword = psListGetAndIncrement(keywordsIter))) {
-        const char *value = psMetadataLookupStr(&mdok, header, keyword);
-        if (first) {
-            psStringAppend(&contentsKey, "%s", value);
-            first = false;
-        } else {
-            psStringAppend(&contentsKey, "_%s", value);
-        }
-    }
-    psFree(keywordsIter);
-    psFree(keywords);
-    psTrace(__func__, 5, "Looking up %s in the CONTENTS.\n", contentsKey);
-    const char *content = psMetadataLookupStr(&mdok, contents, contentsKey);
+    contentKey = psStringCopy(contentKey); // So that we're not altering something someone else might use...
+
+    // Replace some concept names
+    if (strstr(contentKey, "{CHIP.NAME}")) {
+        if (!chip) {
+            psError(PS_ERR_IO, true, "CONTENT in FILE refers to CHIP.NAME, but no chip was provided.\n");
+            psFree(contentKey);
+            return NULL;
+        }
+        const char *name = psMetadataLookupStr(&mdok, chip->concepts, "CHIP.NAME");
+        if (mdok && name && strlen(name) > 0) {
+            contentKey = psStringSubstitute(contentKey, name, "{CHIP.NAME}");
+        }
+    }
+
+    if (strstr(contentKey, "{CELL.NAME}")) {
+        if (!chip) {
+            psError(PS_ERR_IO, true, "CONTENT in FILE refers to CELL.NAME, but no chip was provided.\n");
+            psFree(contentKey);
+            return NULL;
+        }
+        const char *name = psMetadataLookupStr(&mdok, cell->concepts, "CELL.NAME");
+        if (mdok && name && strlen(name) > 0) {
+            contentKey = psStringSubstitute(contentKey, name, "{CELL.NAME}");
+        }
+    }
+
+    psTrace(__func__, 5, "Looking up %s in the CONTENTS.\n", contentKey);
+    const char *content = psMetadataLookupStr(&mdok, contents, contentKey);
     if (!mdok || !content || strlen(content) == 0) {
-        psError(PS_ERR_IO, true, "Unable to find %s in the CONTENTS.\n", contentsKey);
-        return NULL;
-    }
-    psFree(contentsKey);
+        psFree(contentKey);
+        psError(PS_ERR_IO, true, "Unable to find %s in the CONTENTS.\n", contentKey);
+        return NULL;
+    }
+
+    psFree(contentKey);
 
     return content;
@@ -253,5 +267,5 @@
                            pmFPALevel level, // The level at which to add the HDU
                            const char *contents, // The contents line, consisting of a list of chip:cell
-                           psMetadata *format // Camera format configuration
+                           const psMetadata *format // Camera format configuration
                           )
 {
@@ -364,5 +378,5 @@
 }
 
-static pmFPALevel hduLevel(psMetadata *format // The camera format configuration
+static pmFPALevel hduLevel(const psMetadata *format // The camera format configuration
                           )
 {
@@ -442,125 +456,16 @@
 }
 
-bool pmFPAAddSourceFromView(pmFPA *fpa,   // The FPA
-                            const pmFPAview *phuView, // The view, corresponding to the PHU
-                            psMetadata *format // Format of file
+
+// This is the engine for the pmFPAAddSourceFrom{Header,View} functions
+// It returns a view corresponding to the PHU
+static pmFPAview *addSource(pmFPA *fpa,       // The FPA
+                            const pmFPAview *phuView, // The view corresponding to the PHU, or NULL
+                            const psMetadata *header, // The PHU header, or NULL
+                            const psMetadata *format // Format of file
                            )
 {
-    PS_ASSERT_PTR_NON_NULL(fpa, false);
-    PS_ASSERT_PTR_NON_NULL(phuView, false);
-    PS_ASSERT_PTR_NON_NULL(format, false);
-
-    // Where does the PHU go?
-    bool mdok = true;                   // Status of MD lookup
-    psMetadata *fileInfo = psMetadataLookupMD(&mdok, format, "FILE"); // File information from the format
-    if (!mdok || !fileInfo) {
-        psError(PS_ERR_IO, false, "Unable to find FILE information in the camera format configuration.\n");
-        return false;
-    }
-    const char *phuType = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // What is the PHU?
-    if (!mdok || strlen(phuType) == 0) {
-        psError(PS_ERR_IO, false, "Unable to find PHU in the FILE information of the camera format.\n");
-        return false;
-    }
-
-    // Generate the PHU
-    pmHDU *phdu = pmHDUAlloc("PHU");    // The primary header data unit
-    phdu->format = psMemIncrRefCounter(format);
-
-    // Put in the PHU
-    pmChip *chip = NULL;                // The chip that corresponds to the PHU
-    pmCell *cell = NULL;                // The cell that corresponds to the PHU
-    if (strcasecmp(phuType, "FPA") == 0) {
-        addHDUtoFPA(fpa, phdu);
-        psFree(phdu);
-    } else {
-        psArray *chips = fpa->chips;    // Array of chips
-        if (phuView->chip < 0 || phuView->chip > chips->n) {
-            psError(PS_ERR_IO, true, "PHU specified by the camera format requires specification of a "
-                    "particular chip, which cannot be determined from the view (%d).\n", phuView->chip);
-            psFree(phdu);
-            return false;
-        }
-        chip = chips->data[phuView->chip];
-        if (strcasecmp(phuType, "CHIP") == 0) {
-            addHDUtoChip(chip, phdu);
-            psFree(phdu);
-        } else if (strcasecmp(phuType, "CELL") == 0) {
-            psArray *cells = chip->cells; // Array of cells
-            if (phuView->cell < 0 || phuView->cell > cells->n) {
-                psError(PS_ERR_IO, true, "PHU specified by the camera format requires specification of a "
-                        "particular cell, which cannot be determined from the view (%d).\n", phuView->cell);
-                psFree(phdu);
-                return false;
-            }
-            addHDUtoCell(cell, phdu);
-            psFree(phdu);
-        } else {
-            psError(PS_ERR_IO, true, "PHU in the camera configuration format is not FPA, CHIP or CELL.\n");
-            psFree(phdu);
-            return false;
-        }
-    }
-
-    // Put in the extensions
-    pmFPALevel level = hduLevel(format);// The level for the HDUs to go
-    if (level == PM_FPA_LEVEL_NONE) {
-        // No extensions.  We're done unless PHU == FPA, in which case we need to process the contents
-        if (strcasecmp(phuType, "FPA") == 0) {
-            const char *content = psMetadataLookupStr(&mdok, format, "CONTENTS"); // Contents of the FITS file
-            if (!mdok || !content || strlen(content) == 0) {
-                psError(PS_ERR_IO, true, "Unable to find CONTENTS of type STR in the camera "
-                        "format configuration.\n");
-                return false;
-            }
-            if (processContents(fpa, chip, cell, phdu, level, content, format) < 0) {
-                psError(PS_ERR_IO, false, "Error setting contents.\n");
-                return false;
-            }
-        }
-        return true;
-    }
-    psMetadata *contents = psMetadataLookupMD(&mdok, format, "CONTENTS"); // The contents of the FITS file
-    if (!mdok || !contents) {
-        psError(PS_ERR_IO, false, "Unable to find CONTENTS in the camera format configuration.\n");
-        return false;
-    }
-
-    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
-    psMetadataItem *contentsItem = NULL;// Item from the contents iteration
-    while ((contentsItem = psMetadataGetAndIncrement(contentsIter))) {
-        if (contentsItem->type != PS_DATA_STRING) {
-            psLogMsg(__func__, PS_LOG_WARN, "Content item %s is not of type STR --- ignored.\n",
-                     contentsItem->name);
-            continue;
-        }
-        pmHDU *hdu = pmHDUAlloc(contentsItem->name); // HDU to add
-        hdu->format = psMemIncrRefCounter(format);
-        const char *content = contentsItem->data.V; // The content data
-        if (processContents(fpa, chip, cell, hdu, level, content, format) < 0) {
-            psError(PS_ERR_IO, false, "Error setting contents for %s", contentsItem->name);
-            psFree(hdu);
-            psFree(contentsIter);
-
-            return false;
-        }
-        psFree(hdu);
-    }
-    psFree(contentsIter);
-
-    return true;
-}
-
-
-
-// Add an input file to the FPA
-pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, // The FPA
-                                    psMetadata *phu, // Primary header of file
-                                    psMetadata *format // Format of file
-                                   )
-{
-    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
-    PS_ASSERT_PTR_NON_NULL(phu, NULL);
-    PS_ASSERT_PTR_NON_NULL(format, NULL);
+    assert(fpa);
+    assert(phuView || header);
+    assert(format);
 
     bool mdok = true;                   // Status from metadata lookups
@@ -571,22 +476,5 @@
     }
 
-    // Check the name of the FPA
-    psString newFPAname = phuNameFromHeader("FPA.NAME", fileInfo, phu); // New name for the FPA
-    if (!newFPAname) {
-        psError(PS_ERR_IO, true, "Unable to determine FPA.NAME");
-        return NULL;
-    }
-
-    // is FPAname already defined, new name must match it
-    // XXX if not, is it an error?
-    const char *currentFPAname = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.NAME"); // Current name
-    if (mdok && currentFPAname && strlen(currentFPAname) > 0 && strcmp(currentFPAname, newFPAname) != 0) {
-        psLogMsg(__func__, PS_LOG_WARN, "FPA.NAME for new source (%s) doesn't match FPA.NAME for current "
-                 "fpa (%s).\n", newFPAname, currentFPAname);
-    }
-    psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.NAME", PS_META_REPLACE, "Name of FPA", newFPAname);
-    psFree(newFPAname);                 // Drop reference
-
-    // Where does the PHU go?
+    // At what level does the PHU go?
     const char *phuType = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // What is the PHU?
     if (!mdok || strlen(phuType) == 0) {
@@ -594,14 +482,18 @@
         return NULL;
     }
+
+    // Prepare the PHU to be placed in the camera hierarchy
     pmHDU *phdu = pmHDUAlloc("PHU");    // The primary header data unit
-    phdu->header = psMemIncrRefCounter(phu);
-    phdu->format = psMemIncrRefCounter(format);
-    // Generate the view
-    pmFPAview *view = pmFPAviewAlloc(0); // The FPA view corresponding to the PHU, to be returned
-    view->chip = -1;
-    view->cell = -1;
-    view->readout = -1;
-
-    // And what are the individual extensions?
+    // Casting to psPtr to avoide "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+    // pointer target type"
+    phdu->header = psMemIncrRefCounter((const psPtr)header);
+    phdu->format = psMemIncrRefCounter((const psPtr)format);
+    pmFPAview *view = pmFPAviewAlloc(0); // View, to be returned
+    if (phuView) {
+        // Copy the view, for the case where we're given a header.
+        *view = *phuView;
+    }
+
+    // And at what level do the individual extensions go?
     const char *extType = psMetadataLookupStr(&mdok, fileInfo, "EXTENSIONS"); // What's in the extns?
     if (!mdok || strlen(extType) == 0) {
@@ -611,8 +503,23 @@
     }
 
-
-    // This is a special case: PHU=FPA and EXTENSIONS=NONE.  The only reason to do this is in the case of
-    // single CCD imagers, 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.
+    // 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) {
         const char *contents = psMetadataLookupStr(&mdok, format, "CONTENTS"); // The contents of the file
@@ -628,14 +535,13 @@
             psFree(phdu);
             psFree(view);
-
             return NULL;
         }
-
         psFree(phdu);
+
         return view;
     }
 
-    // In all other cases, the CONTENTS is of type METADATA, and is either a menu if EXTENSIONS=NONE, or
-    // a list of extensions otherwise.
+    // 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 = psMetadataLookupMD(&mdok, format, "CONTENTS"); // The contents of the FITS file
     if (!mdok || !contents) {
@@ -649,18 +555,109 @@
             }
         }
-
+        psFree(phdu);
         psFree(view);
         return NULL;
     }
 
-    // No extensions --- only have the PHU.  In this case, the CONTENTS is a menu, with CONTENT indicating
-    // header keywords that provides a key to the CONTENTS menu.
+    // 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) {
-        // We have already dealt with the case PHU=FPA, in a special case, above.
-        const char *content = getContent(fileInfo, contents, phu); // The content: string of chip:cell pairs
-
-        // Need to look up what chip we have.
-        psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, phu);
-        psTrace(__func__, 5, "This is chip %s\n", chipName);
+        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(__func__, 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;
+            }
+            pmChip *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);
+        }
+
+        const char *content = getContent(fileInfo, contents, chip, cell); // The 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);
+
+        return view;
+    }
+
+    // Case 3: EXTENSIONS=CHIP or EXTENSIONS=CELL.  We have extensions that we iterate through.  The CONTENTS
+    // is a list of extensions.
+    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) {
@@ -669,59 +666,4 @@
             return NULL;
         }
-        pmChip *chip = fpa->chips->data[chipNum]; // Chip of interest
-        view->chip = chipNum;
-
-        pmCell *cell = NULL;            // Cell of interest
-
-        pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU to be added
-        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, phu);
-            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];
-            view->cell = cellNum;
-            psFree(cellName);
-        } else {
-            psError(PS_ERR_IO, true, "PHU is not FPA, CHIP or CELL.\n");
-            psFree(view);
-            return NULL;
-        }
-        psFree(chipName);
-
-        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);
-        return view;
-    }
-
-
-    // From here on, we have extensions that we iterate through.  The CONTENTS is a list of extensions.
-    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, phu); // 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;
@@ -730,5 +672,5 @@
             addHDUtoChip(chip, phdu);
         } else if (strcasecmp(phuType, "CELL") == 0) {
-            psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, phu); // Name of the cell
+            psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, header); // Name of the cell
             int cellNum = pmChipFindCell(chip, cellName); // Cell number
             if (cellNum == -1) {
@@ -764,5 +706,7 @@
 
         pmHDU *hdu = pmHDUAlloc(extName); // The extension
-        hdu->format = psMemIncrRefCounter(format);
+        // 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) {
@@ -778,9 +722,62 @@
 
     if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, true, NULL)) {
-        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from defaults for FPA %s.  Attempting to "
-                "proceed anyway.\n", currentFPAname);
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to read concepts from defaults for FPA.  Attempting to "
+                 "proceed anyway.\n");
     }
 
     return view;
+
+}
+
+
+bool pmFPAAddSourceFromView(pmFPA *fpa,   // The FPA
+                            const pmFPAview *phuView, // The view, corresponding to the PHU
+                            const psMetadata *format // Format of file
+                           )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(phuView, false);
+    PS_ASSERT_PTR_NON_NULL(format, false);
+
+    return addSource(fpa, phuView, NULL, format) ? true : false;
+}
+
+
+
+// Add an input file to the FPA
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, // The FPA
+                                    psMetadata *phu, // Primary header of file
+                                    const psMetadata *format // Format of file
+                                   )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(phu, NULL);
+    PS_ASSERT_PTR_NON_NULL(format, NULL);
+
+    bool mdok = true;                   // Status from metadata lookups
+    psMetadata *fileInfo = psMetadataLookupMD(&mdok, format, "FILE"); // The file information
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return NULL;
+    }
+
+    // Check the name of the FPA
+    psString newFPAname = phuNameFromHeader("FPA.NAME", fileInfo, phu); // New name for the FPA
+    if (!newFPAname) {
+        psError(PS_ERR_IO, true, "Unable to determine FPA.NAME");
+        return NULL;
+    }
+
+    // is FPAname already defined, new name must match it; otherwise, warn the user that something potentially
+    // bad is happening.
+    const char *currentFPAname = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.NAME"); // Current name
+    if (mdok && currentFPAname && strlen(currentFPAname) > 0 && strcmp(currentFPAname, newFPAname) != 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "FPA.NAME for new source (%s) doesn't match FPA.NAME for current "
+                 "fpa (%s).\n", newFPAname, currentFPAname);
+    }
+    psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.NAME", PS_META_REPLACE, "Name of FPA", newFPAname);
+    psFree(newFPAname);                 // Drop reference
+
+    return addSource(fpa, NULL, phu, format);
 }
 
