Index: /trunk/psModules/src/config/Makefile.am
===================================================================
--- /trunk/psModules/src/config/Makefile.am	(revision 10964)
+++ /trunk/psModules/src/config/Makefile.am	(revision 10965)
@@ -6,4 +6,5 @@
     pmConfig.c \
     pmConfigRecipes.c \
+    pmConfigCamera.c \
     pmVersion.c
 
@@ -11,4 +12,5 @@
     pmConfig.h \
     pmConfigRecipes.h \
+    pmConfigCamera.h \
     pmVersion.h
 
Index: /trunk/psModules/src/config/pmConfig.c
===================================================================
--- /trunk/psModules/src/config/pmConfig.c	(revision 10964)
+++ /trunk/psModules/src/config/pmConfig.c	(revision 10965)
@@ -4,6 +4,6 @@
  *  @author EAM (IfA)
  *
- *  @version $Revision: 1.69 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2007-01-03 02:58:38 $
+ *  @version $Revision: 1.70 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-01-08 22:26:06 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -26,9 +26,18 @@
 #include "pmConfig.h"
 #include "pmConfigRecipes.h"
+#include "pmConfigCamera.h"
 
 #define PS_SITE "PS_SITE"         // Name of the environment variable containing the site config file
 #define PS_DEFAULT_SITE ".ipprc"  // Default site config file
 
+static bool readCameraConfig = true;    // Read the camera config on startup (with pmConfigRead)?
 static psArray *configPath = NULL;      // Search path for configuration files
+
+bool pmConfigReadParamsSet(bool newReadCameraConfig)
+{
+    bool oldReadCameraConfig = readCameraConfig;
+    readCameraConfig = newReadCameraConfig;
+    return oldReadCameraConfig;
+}
 
 static void configFree(pmConfig *config)
@@ -38,4 +47,6 @@
     psFree(config->camera);
     psFree(config->cameraName);
+    psFree(config->format);
+    psFree(config->formatName);
     psFree(config->recipes);
     psFree(config->recipeSymbols);
@@ -53,4 +64,6 @@
     config->camera = NULL;
     config->cameraName = NULL;
+    config->format = NULL;
+    config->formatName = NULL;
     config->recipes = psMetadataAlloc();
     config->recipesRead = PM_RECIPE_SOURCE_NONE;
@@ -207,4 +220,65 @@
 
     psFree (realName);
+    return true;
+}
+
+// Read metadata config files in a metadata
+// The metadata contains file names, which will be replaced with the metadata that are in the files.
+static bool metadataReadFiles(psMetadata *source, // Source metadata
+                              const char *description // Description, for error messages
+                             )
+{
+    assert(source);
+    psMetadataIterator *iter = psMetadataIteratorAlloc(source, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type == PS_DATA_METADATA) {
+            continue;       // We've already read it
+        }
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Element %s in %s metadata is not of type STR.\n",
+                    item->name, description);
+            continue;
+        }
+
+        psTrace("config", 2, "Reading %s %s: %s\n", description, item->name, item->data.str);
+        psMetadata *new = NULL;         // New metadata
+        if (!pmConfigFileRead(&new, item->data.str, item->name)) {
+            psLogMsg("psModules.config", PS_LOG_WARN, "Trouble reading reading %s %s --- "
+                     "ignored.\n", description, item->name);
+            psFree(new);
+            psMetadataRemoveKey(source, item->name); // Take it out, so it will not trouble us again
+            continue;
+        }
+
+        // Muck around under the hood to replace the filename with the metadata; don't try this at home, kids
+        item->type = PS_DATA_METADATA;
+        psFree(item->data.str);
+        item->data.md = new;
+    }
+    psFree(iter);
+
+    return true;
+}
+
+// Read the formats for a camera
+static bool cameraReadFormats(psMetadata *camera, // Camera for which to read the formats
+                              const char *name // Name of the camera, for error messages
+                             )
+{
+    assert(camera);
+    assert(name);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, camera, "FORMATS"); // Formats
+    if (!mdok || !formats) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FORMATS in camera configuration %s.\n", name);
+        return false;
+    }
+    if (!metadataReadFiles(formats, "camera format")) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n", name);
+        return false;
+    }
+
     return true;
 }
@@ -274,6 +348,4 @@
     }
 
-
-
     // Set options based on the site configuration.
     {
@@ -437,4 +509,57 @@
             pmConfigFileRead(&config->camera, cameraFile, "camera");
             psArgumentRemove(argNum, config->argc, config->argv);
+
+            // Read in the formats
+            if (!cameraReadFormats(config->camera, cameraFile)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n",
+                        cameraFile);
+                psFree(config);
+                return NULL;
+            }
+
+            psMetadataAddMetadata(cameras, PS_LIST_HEAD, cameraFile, PS_META_REPLACE,
+                                  "Camera specified on command line", config->camera);
+
+            if (!pmConfigCameraMosaickedVersions(config->site, cameraFile)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to generate mosaicked versions of specified camera %s.\n",
+                        cameraFile);
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+
+    // Read the camera configurations, if not already defined, and not turned off
+    if (!config->camera && readCameraConfig) {
+        bool mdok;                      // Status of MD lookup
+        psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->site, "CAMERAS"); // List of cameras
+        if (!mdok || !cameras) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the site configuration.\n");
+            return false;
+        }
+
+        if (!metadataReadFiles(cameras, "camera configuration")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within site configuration.\n");
+            psFree(config);
+            return NULL;
+        }
+
+        // Now fill in the formats
+        psMetadataIterator *iter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;           // Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            assert(item->type == PS_DATA_METADATA);
+            if (!cameraReadFormats(item->data.md, item->name)) {
+                psWarning("Unable to read formats for camera %s: removed.\n", item->name);
+                psMetadataRemoveKey(cameras, item->name);
+            }
+        }
+        psFree(iter);
+
+        if (!pmConfigCameraMosaickedVersionsAll(config->site)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate mosaicked versions of cameras.\n");
+            psFree(config);
+            return NULL;
         }
     }
@@ -448,11 +573,11 @@
 
     // load command-line options of the form -recipe NAME RECIPE
-    pmConfigLoadRecipeArguments (config);
+    pmConfigLoadRecipeArguments(config);
 
     // read in command-line options to specific recipe values
-    pmConfigLoadRecipeOptions (config, "-D");
-    pmConfigLoadRecipeOptions (config, "-Di");
-    pmConfigLoadRecipeOptions (config, "-Df");
-    pmConfigLoadRecipeOptions (config, "-Db");
+    pmConfigLoadRecipeOptions(config, "-D");
+    pmConfigLoadRecipeOptions(config, "-Di");
+    pmConfigLoadRecipeOptions(config, "-Df");
+    pmConfigLoadRecipeOptions(config, "-Db");
 
 
@@ -589,6 +714,8 @@
 }
 
+
 // Given a camera and a header, see if any of the camera formats match the header
 static bool formatFromHeader(psMetadata **format, // Format to return
+                             psString *name, // Name to return
                              psMetadata *camera, // Camera configuration
                              const psMetadata *header, // FITS header
@@ -610,28 +737,27 @@
         return false;
     }
+
+    if (!metadataReadFiles(formats, "camera format")) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read cameras formats within camera configuration.\n");
+        return false;
+    }
+
     // Iterate over the formats
     psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL);
     psMetadataItem *formatsItem = NULL; // Item from formats
     while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
-        if (formatsItem->type != PS_DATA_STRING) {
-            psError(PS_ERR_UNKNOWN, false, "In camera %s, camera format %s is not of type STR", cameraName, formatsItem->name);
-            return false;
-        }
-        psTrace("psModules.config", 5, "Reading camera format for %s...\n", formatsItem->name);
-        psMetadata *testFormat = NULL;  // Format to test against what we've got
-        if (!pmConfigFileRead(&testFormat, formatsItem->data.V, formatsItem->name)) {
-            psError(PS_ERR_UNKNOWN, false, "Trouble reading reading camera format %s", formatsItem->name);
-            psFree(testFormat);
-            return false;
-        }
+        assert(formatsItem->type == PS_DATA_METADATA); // Since we have just read it in or deleted it
+        psMetadata *testFormat = formatsItem->data.md; // Format to test against
 
         if (pmConfigValidateCameraFormat(testFormat, header)) {
             if (!*format) {
-                psLogMsg("psModules.config", PS_LOG_INFO, "Camera %s, format %s matches header.\n", cameraName,
-                         formatsItem->name);
+                psLogMsg("psModules.config", PS_LOG_INFO, "Camera %s, format %s matches header.\n",
+                         cameraName, formatsItem->name);
                 *format = psMemIncrRefCounter(testFormat);
+                *name = psStringCopy(formatsItem->name);
                 result = true;
             } else {
-                psLogMsg("psModules.config", PS_LOG_WARN, "Camera %s, format %s also matches header --- ignored.\n",
+                psLogMsg("psModules.config", PS_LOG_WARN,
+                         "Camera %s, format %s also matches header --- ignored.\n",
                          cameraName, formatsItem->name);
             }
@@ -639,10 +765,10 @@
             psErr *error = psErrorLast();
             if (error->code != PS_ERR_NONE) {
-                psError (PS_ERR_UNKNOWN, false, "Error in config scripts for camera %s, format %s\n", cameraName, formatsItem->name);
+                psError (PS_ERR_UNKNOWN, false, "Error in config scripts for camera %s, format %s\n",\
+                         cameraName, formatsItem->name);
                 return false;
             }
             psFree(error);
         }
-        psFree(testFormat);
     }
     psFree(formatsIter);
@@ -657,13 +783,20 @@
     PS_ASSERT_PTR_NON_NULL(header, NULL);
 
+
     psMetadata *format = NULL;          // The winning format
-    bool mdok = false;                  // Metadata lookup status
+    psString name = NULL;               // Name of the winning format
 
     // If we don't know what sort of camera we have, we try all that we know
     if (! config->camera) {
+        bool mdok;                      // Metadata lookup status
         psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->site, "CAMERAS");
-        if (! mdok) {
+        if (! mdok || !cameras) {
             psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
-            return false;
+            return NULL;
+        }
+
+        if (!metadataReadFiles(cameras, "camera configuration")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within site configuration.\n");
+            return NULL;
         }
 
@@ -675,23 +808,11 @@
             psTrace("psModules.config", 3, "Inspecting camera %s (%s)\n", camerasItem->name,
                     camerasItem->comment);
-            if (camerasItem->type != PS_DATA_STRING) {
-                psLogMsg("psModules.config", PS_LOG_WARN, "Camera configuration for %s in CAMERAS is not of type STR "
-                         "--- ignored.\n", camerasItem->name);
-                continue;
-            }
-
-            psTrace("psModules.config", 5, "Reading camera configuration for %s...\n", camerasItem->name);
-            psMetadata *testCamera = NULL; // Camera to test against what we've got:
-
-            if (!pmConfigFileRead(&testCamera, camerasItem->data.V, camerasItem->name)) {
-                psLogMsg("psModules.config", PS_LOG_WARN, "Trouble reading reading camera configuration %s --- "
-                         "ignored.\n", camerasItem->name);
-                psFree(testCamera);
-                continue;
-            }
-
-            if (formatFromHeader(&format, testCamera, header, camerasItem->name)) {
+            assert(camerasItem->type == PS_DATA_METADATA); // It should be because we've read it in or deleted
+            psMetadata *testCamera = camerasItem->data.md; // Camera to test against what we've got:
+            if (formatFromHeader(&format, &name, testCamera, header, camerasItem->name)) {
                 config->camera = psMemIncrRefCounter(testCamera);
                 config->cameraName = psStringCopy(camerasItem->name);
+                config->formatName = name;
+                config->format = psMemIncrRefCounter(format);
             } else {
                 psErr *error = psErrorLast();
@@ -702,5 +823,4 @@
                 psFree(error);
             }
-            psFree(testCamera);
         } // Done looking at all cameras
         psFree(camerasIter);
@@ -717,9 +837,11 @@
 
     // Otherwise, try the specific camera
-    if (!formatFromHeader(&format, config->camera, header, config->cameraName)) {
+    if (!formatFromHeader(&format, &name, config->camera, header, config->cameraName)) {
         psError(PS_ERR_IO, true, "Unable to find a format with the specified camera (%s) that matches the "
                 "given header.\n", config->cameraName);
         return NULL;
     }
+    config->formatName = name;
+    config->format = psMemIncrRefCounter(format);
     return format;
 }
Index: /trunk/psModules/src/config/pmConfig.h
===================================================================
--- /trunk/psModules/src/config/pmConfig.h	(revision 10964)
+++ /trunk/psModules/src/config/pmConfig.h	(revision 10965)
@@ -9,6 +9,6 @@
 /// @author Eugene Magnier, IfA
 ///
-/// @version $Revision: 1.22 $ $Name: not supported by cvs2svn $
-/// @date $Date: 2006-12-11 20:59:48 $
+/// @version $Revision: 1.23 $ $Name: not supported by cvs2svn $
+/// @date $Date: 2007-01-08 22:26:06 $
 ///
 /// Copyright 2005-2006 Institute for Astronomy, University of Hawaii
@@ -42,5 +42,7 @@
     psMetadata *site;                   ///< Site configuration
     psMetadata *camera;                 ///< Camera specification
-    const char *cameraName;             ///< Camera name
+    psString cameraName;                ///< Camera name
+    psMetadata *format;                 ///< Camera format description
+    psString formatName;                ///< Camera format name
     psMetadata *recipes;                ///< Recipes for processing
     psMetadata *arguments;              ///< Processed command-line arguments
@@ -49,7 +51,7 @@
     int *argc;                          ///< Number of command-line arguments
     char **argv;                        ///< Command-line arguments (raw version)
-    char *defaultRecipe;  ///< name of top-level recipe for this program
+    const char *defaultRecipe;          ///< name of top-level recipe for this program
     // Private members
-    pmRecipeSource recipesRead;  ///< Which recipe sources have been read
+    pmRecipeSource recipesRead;         ///< Which recipe sources have been read
     psMetadata *recipeSymbols;          ///< Where each recipe came from
 }
Index: /trunk/psModules/src/config/pmConfigCamera.c
===================================================================
--- /trunk/psModules/src/config/pmConfigCamera.c	(revision 10965)
+++ /trunk/psModules/src/config/pmConfigCamera.c	(revision 10965)
@@ -0,0 +1,385 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmFPALevel.h"
+#include "pmConcepts.h"
+
+#include "pmConfigCamera.h"
+
+
+// Update the sources of the concepts, so that they all come from the headers.
+static void updateConceptsSources(psList *concepts, // List of concepts
+                                  psMetadata *translation, // Header concepts
+                                  psMetadata *database, // Database concepts
+                                  psMetadata *defaults, // Defaults concepts
+                                  psMetadata *formats, // Concepts formats
+                                  pmFPALevel level // Mosaic level
+                                 )
+{
+    assert(concepts);
+    assert(translation);
+    assert(level == PM_FPA_LEVEL_CHIP || level == PM_FPA_LEVEL_FPA);
+
+    psListIterator *iter = psListIteratorAlloc(concepts, PS_LIST_HEAD, false); // Iterator
+    psString concept;                   // Concept, from iteration
+    while ((concept = psListGetAndIncrement(iter))) {
+        if (strcmp(concept, "CELL.BIASSEC") != 0 && strcmp(concept, "CELL.TRIMSEC") != 0 &&
+                strcmp(concept, "CELL.XPARITY") != 0 && strcmp(concept, "CELL.YPARITY") != 0 &&
+                strcmp(concept, "CELL.X0") != 0 && strcmp(concept, "CELL.Y0") != 0 &&
+                (level != PM_FPA_LEVEL_FPA ||
+                 (strcmp(concept, "CHIP.XPARITY") != 0 && strcmp(concept, "CHIP.YPARITY") != 0 &&
+                  strcmp(concept, "CHIP.X0") != 0 && strcmp(concept, "CHIP.Y0") != 0))) {
+            // We add these ourselves
+            psMetadataAddStr(translation, PS_LIST_TAIL, concept, PS_META_REPLACE, NULL, concept);
+        }
+        if (database && psMetadataLookup(database, concept)) {
+            psMetadataRemoveKey(database, concept);
+        }
+        if (defaults && psMetadataLookup(defaults, concept)) {
+            psMetadataRemoveKey(defaults, concept);
+        }
+    }
+    psFree(iter);
+
+    return;
+}
+
+
+// Generate a mosaicked version of a camera configuration
+static bool mosaickedVersion(psMetadata *oldCameras, // Old list of camera configurations
+                             psMetadata *newCameras, // New list of camera configurations
+                             const char *name, // Name of original camera configuration
+                             pmFPALevel level // Level
+                            )
+{
+    assert(oldCameras);
+    assert(newCameras);
+    assert(name);
+    assert(level == PM_FPA_LEVEL_CHIP || level == PM_FPA_LEVEL_FPA);
+
+    // See if the old one is there
+    psMetadata *camera = psMetadataLookupMetadata(NULL, oldCameras, name); // The camera configuration
+    if (!camera) {
+        return false;
+    }
+
+    // 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");
+    if (psMetadataLookup(oldCameras, newName)) {
+        return true;
+    }
+
+    psMetadata *new = psMetadataCopy(NULL, camera); // Copy of the camera
+    bool mdok;                          // Status of MD lookups
+
+    // Fix up the contents of the FPA
+    psMetadata *fpa = psMetadataLookupMetadata(&mdok, new, "FPA"); // FPA in the camera configuration
+    if (!mdok || !fpa) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find FPA within camera configuration.");
+        psFree(new);
+        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;
+    default:
+        psAbort(PS_FILE_LINE, "Should never get here.\n");
+    }
+
+    // Update the camera formats appropriately
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, new, "FORMATS"); // FORMATS in the configuration
+    assert(mdok && formats);            // It had better be there --- we've already read them in
+    psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *formatsItem = NULL; // Item from iteration
+    while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
+        assert(formatsItem->type == PS_DATA_METADATA); // We should have read it by now!
+        psMetadata *format = formatsItem->data.V; // The camera format
+
+        // Add a RULE
+        psMetadata *rule = psMetadataLookupMetadata(&mdok, format, "RULE"); // Way to identify format from PHU
+        if (!mdok || !rule) {
+            psWarning("Couldn't find RULE in the camera format %s.\n", formatsItem->name);
+            continue;
+        }
+        switch (level) {
+        case PM_FPA_LEVEL_CHIP:
+            psMetadataAddStr(rule, PS_LIST_TAIL, "PSMOSAIC", 0, "Mosaicked level", "CHIP");
+            break;
+        case PM_FPA_LEVEL_FPA:
+            psMetadataAddStr(rule, PS_LIST_TAIL, "PSMOSAIC", 0, "Mosaicked level", "FPA");
+            break;
+        default:
+            psAbort(PS_FILE_LINE, "Should never get here.\n");
+        }
+
+        // Fix the FILE information: need to fix the levels for the PHU and EXTENSIONS.
+        // If EXTENSIONS is NONE, then we need to change the CONTENT specifier to point to the chip name.
+        psMetadata *file = psMetadataLookupMetadata(&mdok, format, "FILE"); // File information
+        if (!mdok || !file) {
+            psWarning("Couldn't find FILE in the camera format %s.\n", formatsItem->name);
+            continue;
+        }
+        psMetadataItem *phuItem = psMetadataLookup(file, "PHU"); // PHU level
+        if (!phuItem || phuItem->type != PS_DATA_STRING) {
+            psWarning("Couldn't find PHU of type STR in the FILE information in the camera format %s.\n",
+                      formatsItem->name);
+            continue;
+        }
+        if (strcasecmp(phuItem->data.str, "CELL") == 0 ||
+                (level == PM_FPA_LEVEL_FPA && (strcasecmp(phuItem->data.str, "CHIP") == 0))) {
+            psFree(phuItem->data.str);
+            if (level == PM_FPA_LEVEL_CHIP) {
+                phuItem->data.str = psStringCopy("CHIP");
+            } else {
+                phuItem->data.str = psStringCopy("FPA");
+            }
+        }
+        psMetadataItem *extensionsItem = psMetadataLookup(file, "EXTENSIONS"); // Extensions level
+        if (!extensionsItem || extensionsItem->type != PS_DATA_STRING) {
+            psWarning("Couldn't find EXTENSIONS of type STR in the FILE information "
+                      "in the camera format %s.\n", formatsItem->name);
+            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) {
+                psFree(extensionsItem->data.str);
+                extensionsItem->data.str = psStringCopy("CHIP");
+            }
+            break;
+        case PM_FPA_LEVEL_FPA:
+            psFree(extensionsItem->data.str);
+            extensionsItem->data.str = psStringCopy("NONE");
+            break;
+        default:
+            psAbort(PS_FILE_LINE, "Should never get here.\n");
+        }
+
+        // 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;
+                    }
+                    psStringAppend(&contentsLine, "%s:MosaickedCell:_mosaic ", fpaItem->name);
+                }
+                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
+            if (!mdok || !contents) {
+                psWarning("Couldn't find CONTENTS in the camera format %s.\n", formatsItem->name);
+                continue;
+            }
+            while (psListLength(contents->list) > 0) {
+                psMetadataRemoveIndex(contents, PS_LIST_TAIL);
+            }
+            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;
+                }
+
+                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
+            }
+            psFree(fpaIter);
+            break;
+        case PM_FPA_LEVEL_FPA:
+            psMetadataAddStr(format, PS_LIST_TAIL, "CONTENTS", PS_META_REPLACE, NULL,
+                             "MosaickedChip:MosaickedCell:_mosaic");
+            break;
+        default:
+            psAbort(PS_FILE_LINE, "Should never get here.\n");
+        }
+
+        // Fix the cell type
+        psMetadata *cells = psMetadataLookupMetadata(&mdok, format, "CELLS"); // CELLS information
+        if (!mdok || !cells) {
+            psWarning("Couldn't find CELLS of type METADATA in the camera format %s.\n", formatsItem->name);
+            continue;
+        }
+        psMetadata *cell = psMetadataAlloc(); // Cell information
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.TRIMSEC", 0, "Trim section", "TRIMSEC");
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.BIASSEC", 0, "Bias section", "BIASSEC");
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.TRIMSEC.SOURCE", 0, "Trim section source", "HEADER");
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.BIASSEC.SOURCE", 0, "Bias section source", "HEADER");
+        psMetadataAddMetadata(cells, PS_LIST_HEAD, "_mosaic", PS_META_REPLACE,
+                              "Mosaic cell information", cell);
+        psFree(cell);                   // Drop reference
+
+        // Update the concepts, so that they are all stored in the FITS headers, under headers of the same
+        // name as the concept
+        psMetadata *conceptsFormats = psMetadataLookupMetadata(&mdok, format, "FORMATS"); // Concepts FORMATS
+        psMetadata *database = psMetadataLookupMetadata(&mdok, format, "DATABASE"); // DATABASE concepts
+        psMetadata *defaults = psMetadataLookupMetadata(&mdok, format, "DEFAULTS"); // DEFAULTS concepts
+        if (!mdok || !defaults) {
+            psWarning("Couldn't find DEFAULTS of type METADATA in the camera format %s.\n",
+                      formatsItem->name);
+            continue;
+        }
+        psMetadata *translation = psMetadataLookupMetadata(&mdok, format, "TRANSLATION"); // TRANSLATION info
+        if (!mdok || !translation) {
+            psWarning("Couldn't find TRANSLATION of type METADATA in the camera format %s.\n",
+                      formatsItem->name);
+            continue;
+        }
+        psList *concepts = pmConceptsList(PM_FPA_LEVEL_CELL); // List of concepts
+        updateConceptsSources(concepts, translation, database, defaults, conceptsFormats, level);
+        psFree(concepts);
+        concepts = pmConceptsList(PM_FPA_LEVEL_CHIP);
+        updateConceptsSources(concepts, translation, database, defaults, conceptsFormats, level);
+        psFree(concepts);
+        concepts = pmConceptsList(PM_FPA_LEVEL_FPA);
+        updateConceptsSources(concepts, translation, database, defaults, conceptsFormats, level);
+        psFree(concepts);
+
+        // Add in the positioning concepts
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.XPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.YPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.XPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.YPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.X0",      0, NULL, 0);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.Y0",      0, NULL, 0);
+        if (formats) {
+            if (psMetadataLookup(formats, "CELL.X0")) {
+                psMetadataRemoveKey(formats, "CELL.X0");
+            }
+            if (psMetadataLookup(formats, "CELL.Y0")) {
+                psMetadataRemoveKey(formats, "CELL.Y0");
+            }
+        }
+
+        if (level == PM_FPA_LEVEL_FPA) {
+            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.X0", 0, NULL, 0);
+            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.Y0", 0, NULL, 0);
+            if (formats) {
+                if (psMetadataLookup(formats, "CHIP.X0")) {
+                    psMetadataRemoveKey(formats, "CHIP.X0");
+                }
+                if (psMetadataLookup(formats, "CHIP.Y0")) {
+                    psMetadataRemoveKey(formats, "CHIP.Y0");
+                }
+            }
+        }
+
+    }
+    psFree(formatsIter);
+
+    // New camera MUST go to the head of the metadata, so that it will be recognised first
+    // The old camera doesn't contain the PSMOSAIC header, so it will match anything!
+    psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE,
+                          "Automatically generated", new);
+    psFree(newName);
+    psFree(new);
+
+    return true;
+}
+
+
+// Generate the chip mosaicked version of a camera configuration
+bool pmConfigCameraMosaickedVersions(psMetadata *site, // The site configuration
+                                     const char *name // Name of the un-mosaicked camera
+                                    )
+{
+    PS_ASSERT_METADATA_NON_NULL(site, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cameras = psMetadataLookupMetadata(&mdok, site, "CAMERAS"); // List of cameras
+    if (!mdok || !cameras) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the site configuration.\n");
+        return false;
+    }
+    return mosaickedVersion(cameras, cameras, name, PM_FPA_LEVEL_CHIP) &&
+           mosaickedVersion(cameras, cameras, name, PM_FPA_LEVEL_FPA);
+}
+
+
+bool pmConfigCameraMosaickedVersionsAll(psMetadata *site)
+{
+    PS_ASSERT_METADATA_NON_NULL(site, false);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cameras = psMetadataLookupMetadata(&mdok, site, "CAMERAS"); // List of cameras
+    if (!mdok || !cameras) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the site configuration.\n");
+        return false;
+    }
+
+    psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *camerasItem = NULL; // Item from iteration
+    psMetadata *new = psMetadataAlloc();// New cameras to add
+    while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+        assert(camerasItem->type == PS_DATA_METADATA); // Only metadata are allowed here!
+        mosaickedVersion(cameras, new, camerasItem->name, PM_FPA_LEVEL_CHIP);
+        mosaickedVersion(cameras, new, camerasItem->name, PM_FPA_LEVEL_FPA);
+    }
+    psFree(camerasIter);
+
+    // Now put the new cameras at the top of the list of cameras, so they get recognised first
+    camerasIter = psMetadataIteratorAlloc(new, PS_LIST_TAIL, NULL); // Iterator
+    while ((camerasItem = psMetadataGetAndDecrement(camerasIter))) {
+        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, 0);
+    }
+    psFree(camerasIter);
+    psFree(new);
+
+    return true;
+}
+
Index: /trunk/psModules/src/config/pmConfigCamera.h
===================================================================
--- /trunk/psModules/src/config/pmConfigCamera.h	(revision 10965)
+++ /trunk/psModules/src/config/pmConfigCamera.h	(revision 10965)
@@ -0,0 +1,16 @@
+#ifndef PM_CONFIG_CAMERA_H
+#define PM_CONFIG_CAMERA_H
+
+#include <pslib.h>
+
+/// Generate the chip mosaicked version of a particular camera configuration
+bool pmConfigCameraMosaickedVersions(psMetadata *site, // Site configuration
+                                     const char *name // Name of the un-mosaicked camera
+                                    );
+
+/// Generate chip- and fpa-mosaicked versions of all the camera configurations
+bool pmConfigCameraMosaickedVersionsAll(psMetadata *site // Site configuration
+                                       );
+
+
+#endif
Index: /trunk/psModules/src/config/pmConfigRecipes.c
===================================================================
--- /trunk/psModules/src/config/pmConfigRecipes.c	(revision 10964)
+++ /trunk/psModules/src/config/pmConfigRecipes.c	(revision 10965)
@@ -146,5 +146,5 @@
 
         // is a target recipe specified?
-        char *recipeName = NULL;
+        const char *recipeName = NULL;
         char *key;
         psArray *words = psStringSplitArray(config->argv[argNum], ":", false);
