Index: /trunk/psModules/src/camera/pmFPAEnsemble.c
===================================================================
--- /trunk/psModules/src/camera/pmFPAEnsemble.c	(revision 8048)
+++ /trunk/psModules/src/camera/pmFPAEnsemble.c	(revision 8048)
@@ -0,0 +1,297 @@
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmFPA.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAEnsemble.h"
+
+
+static void fpaComponentFree(pmFPAComponent *component)
+{
+    psFree(component->fpa);
+    psFree(component->view);
+    return;
+}
+
+pmFPAComponent *pmFPAComponentAlloc(pmFPA *fpa, pmFPAview *view)
+{
+    pmFPAComponent *component = psAlloc(sizeof(pmFPAComponent));
+    psMemSetDeallocator(component, (psFreeFunc)fpaComponentFree);
+    component->fpa = psMemIncrRefCounter(fpa);
+    component->view = psMemIncrRefCounter(view);
+    return component;
+}
+
+bool pmFPAComponentSetExist(pmFPAComponent *component)
+{
+    pmFPA *fpa = component->fpa;        // The FPA of interest
+    pmFPAview *view = component->view;  // The view of interest
+
+    if (view->chip < 0) {
+        // This iterates on all chips
+        return pmFPASetFileStatus(fpa, true);
+    }
+    psArray *chips = fpa->chips;        // Array of chips
+    if (view->chip >= chips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->chip);
+        return false;
+    }
+    pmChip *chip = chips->data[view->chip]; // Chip of interest
+    if (view->cell < 0) {
+        // This iterates on all cells
+        return pmChipSetFileStatus(chip, true);
+    }
+    psArray *cells = chip->cells;       // Array of cells
+    if (view->cell >= cells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->cell);
+        return false;
+    }
+    pmCell *cell = cells->data[view->cell]; // Cell of interest
+    return pmCellSetFileStatus(cell, true);
+}
+
+bool pmFPAComponentCheckExist(pmFPAComponent *component)
+{
+    pmFPA *fpa = component->fpa;        // The FPA of interest
+    pmFPAview *view = component->view;  // The view of interest
+
+    if (view->chip < 0) {
+        // This iterates on all chips
+        return pmFPACheckFileStatus(fpa);
+    }
+    psArray *chips = fpa->chips;        // Array of chips
+    if (view->chip >= chips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->chip);
+        return false;
+    }
+    pmChip *chip = chips->data[view->chip]; // Chip of interest
+    if (view->cell < 0) {
+        // This iterates on all cells
+        return pmChipCheckFileStatus(chip);
+    }
+    psArray *cells = chip->cells;       // Array of cells
+    if (view->cell >= cells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->cell);
+        return false;
+    }
+    pmCell *cell = cells->data[view->cell]; // Cell of interest
+    return pmCellCheckFileStatus(cell);
+}
+
+
+// Move a cell from one chip to another
+static bool cellMove(pmChip *target,    // Target chip
+                     pmChip *source,    // Source chip
+                     int cellNum        // Number of cell
+                    )
+{
+    assert(cellNum > 0);
+    assert(cellNum < source->cells->n);
+
+    pmCell *sourceCell = source->cells->data[cellNum]; // The source cell
+    pmCell *targetCell = target->cells->data[cellNum]; // The target cell
+    // Switch ownership of the cell
+    psFree(targetCell);
+    target->cells->data[cellNum] = sourceCell;
+    source->cells->data[cellNum] = NULL;
+    sourceCell->parent = target;
+
+    return true;
+}
+
+// Move a chip from one FPA to another
+static bool chipMove(pmFPA *target,     // Target FPA
+                     pmFPA *source,     // Source FPA
+                     int chipNum        // Number of chip
+                    )
+{
+    assert(chipNum > 0);
+    assert(chipNum < source->chips->n);
+
+    pmChip *sourceChip = source->chips->data[chipNum]; // The source chip
+    pmChip *targetChip = target->chips->data[chipNum]; // The target chip
+    // Switch ownership of the chip
+    psFree(targetChip);
+    target->chips->data[chipNum] = sourceChip;
+    source->chips->data[chipNum] = NULL;
+    sourceChip->parent = target;
+
+    return true;
+}
+
+
+
+bool pmFPAComponentCopy(pmFPAComponent *target, pmFPA *source)
+{
+    pmFPA *targetFPA = target->fpa;     // The FPA of interest
+    pmFPAview *view = target->view;     // The view of interest
+
+    if (view->chip < 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "This FPA already exists.\n");
+        return false;
+    }
+
+    psArray *targetChips = targetFPA->chips; // Array of chips
+    if (view->chip >= targetChips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->chip);
+        return false;
+    }
+    if (view->cell < 0) {
+        // Move the entire chip over
+        return chipMove(targetFPA, source, view->chip);
+    }
+    pmChip *targetChip = targetChips->data[view->chip]; // Target chip of interest
+    pmChip *sourceChip = source->chips->data[view->chip]; // Source chip of interest
+    psArray *targetCells = targetChip->cells; // Array of cells
+    if (view->cell >= targetCells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->cell);
+        return false;
+    }
+    // Move the cell over
+    return cellMove(targetChip, sourceChip, view->cell);
+}
+
+
+
+
+static void fpaEnsembleFree(pmFPAEnsemble *ensemble)
+{
+    psFree(ensemble->fpa);
+    psFree(ensemble->fits);
+    psFree(ensemble->fpaToFits);
+    psFree(ensemble->fitsToFpa);
+    return;
+}
+
+pmFPAEnsemble *pmFPAEnsembleAlloc(psArray *filenames, // Array of file names
+                                  pmConfig *config // Configuration
+                                 )
+{
+    int numFiles = filenames->n;        // Number of files
+    PS_ASSERT_INT_POSITIVE(numFiles, NULL);
+
+    // The objects of interest
+    psArray *fits = psArrayAlloc(numFiles); // Array of file pointers
+    fits->n = numFiles;
+    psMetadata *fpas = psMetadataAlloc(); // The focal plane arrays, indexed by FPA.NAME
+
+    // Translations
+    psArray *fitsToFPA = psArrayAlloc(numFiles); // Array with FPAs for each FITS file
+    fitsToFPA->n = numFiles;
+    psMetadata *fpaToFits = psMetadataAlloc(); // The FITS file for each FPA, indexed by FPA.NAME
+
+    for (int i = 0; i < numFiles; i++) {
+        // Open the file, read the header, get the camera format, generate an FPA structure
+        const char *filename = filenames->data[i]; // File name
+        psFits *fp = psFitsOpen(filename, "r"); // File pointer
+        if (!fp) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to open %s --- ignored.\n", filename);
+            continue;
+        }
+        psMetadata *header = psFitsReadHeader(NULL, fp); // Primary header from the FITS file
+        if (!header) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to read header for %s --- ignored.\n", filename);
+            psFitsClose(fp);
+            continue;
+        }
+        psMetadata *format = pmConfigCameraFormatFromHeader(config, header);
+        if (!format) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine camera format for %s --- ignored.\n",
+                     filename);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+        // Construct camera in preparation for reading
+        pmFPA *fpa = pmFPAConstruct(config->camera); // An FPA to contain only this file
+        if (!fpa) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to construct FPA for %s --- ignored.\n", filename);
+            psFree(format);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+        pmFPAview *view = pmFPAAddSourceFromHeader(fpa, header, format); // View corresponding to the PHU
+        if (!view) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to add file to FPA for %s --- ignored.\n", filename);
+            psFree(fpa);
+            psFree(format);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+
+        // Put this into any existing FPA
+        bool mdok;                      // Status of MD lookup
+        const char *fpaName = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.NAME"); // Name of the FPA
+        if (!mdok || !fpaName || strlen(fpaName) == 0) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to find FPA.NAME for %s --- ignored.\n", filename);
+            psFree(view);
+            psFree(fpa);
+            psFree(format);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+
+        psTrace(__func__, 3, "%s --> FPA: %s, chip: %d, cell: %d\n", filename, fpaName,
+                view->chip, view->cell);
+
+        pmFPAComponent *component;      // FPA component for the new file
+        psMetadataItem *check = psMetadataLookup(fpas, fpaName); // Check for an existing FPA with same name
+        if (!check) {
+            // Add in the new FPA
+            psTrace(__func__, 5, "New FPA.\n");
+            component = pmFPAComponentAlloc(fpa, view);
+            psMetadataAddPtr(fpas, PS_LIST_TAIL, fpaName, PS_DATA_UNKNOWN, NULL, component->fpa);
+        } else {
+            // Need to put the appropriate element of the new FPA in the old one.
+            psTrace(__func__, 5, "Adding to extant FPA.\n");
+            pmFPA *oldFPA = check->data.V; // The existing FPA
+            component = pmFPAComponentAlloc(oldFPA, view);
+            // Check that it doesn't already exist
+            if (pmFPAComponentCheckExist(component)) {
+                psLogMsg(__func__, PS_LOG_WARN, "The component for FPA %s in file %s already exists --- "
+                         "ignored.\n", fpaName, filename);
+                psFree(component);
+                psFree(view);
+                psFree(fpa);
+                psFree(format);
+                psFree(header);
+                psFitsClose(fp);
+                continue;
+            }
+
+            if (!pmFPAComponentCopy(component, fpa)) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to copy FPA component for %s --- ignored.\n",
+                         filename);
+                psFree(component);
+                psFree(view);
+                psFree(fpa);
+                psFree(format);
+                psFree(header);
+                psFitsClose(fp);
+                continue;
+            }
+        }
+        pmFPAComponentSetExist(component);
+
+        fits->data[i] = fp;
+        // Fill in the translations
+        fitsToFPA->data[i] = component;
+        psMetadataAddS32(fpaToFits, PS_LIST_TAIL, fpaName, PS_META_DUPLICATE_OK, NULL, i);
+    }
+
+    pmFPAEnsemble *ensemble = psAlloc(sizeof(pmFPAEnsemble)); // The ensemble, to be returned
+    psMemSetDeallocator(ensemble, (psFreeFunc)fpaEnsembleFree);
+
+    ensemble->fits = fits;
+    ensemble->fpa = fpas;
+    ensemble->fitsToFpa = fitsToFPA;
+    ensemble->fpaToFits = fpaToFits;
+
+    return ensemble;
+}
Index: /trunk/psModules/src/camera/pmFPAEnsemble.h
===================================================================
--- /trunk/psModules/src/camera/pmFPAEnsemble.h	(revision 8048)
+++ /trunk/psModules/src/camera/pmFPAEnsemble.h	(revision 8048)
@@ -0,0 +1,41 @@
+#ifndef PM_FPA_ENSEMBLE_H
+#define PM_FPA_ENSEMBLE_H
+
+// An ensemble of FPAs: given a bunch of filenames, we want to generate the FPAs that those files represent,
+// along with the links from one to the other, and the other to the one.
+typedef struct
+{
+    // The data
+    psMetadata *fpa;                    // Focal plane arrays, indexed by FPA.NAME
+    psArray *fits;                      // FITS file pointers
+    // The translations
+    psMetadata *fpaToFits;              // Given an FPA.NAME, what is the FITS file?
+    psArray *fitsToFpa;                 // Given a FITS file, what is the FPA?
+}
+pmFPAEnsemble;
+
+pmFPAEnsemble *pmFPAEnsembleAlloc(psArray *filenames, // Array of file names
+                                  pmConfig *config // Configuration
+                                 );
+
+// A component of an FPA, represented by the FPA and a view.
+typedef struct
+{
+    pmFPA *fpa;                         // Focal plane array
+    pmFPAview *view;                    // View corresponding to the PHU
+}
+pmFPAComponent;
+
+pmFPAComponent *pmFPAComponentAlloc(pmFPA *fpa, pmFPAview *view);
+
+// Set the file_exist flag for this FPA component
+bool pmFPAComponentSetExist(pmFPAComponent *component);
+
+// Check the file_exist flags for this FPA component
+bool pmFPAComponentCheckExist(pmFPAComponent *component);
+
+// Copy a component of the source FPA to the target
+bool pmFPAComponentCopy(pmFPAComponent *target, pmFPA *source);
+
+
+#endif
