Index: /trunk/archive/scripts/src/phase2/Makefile
===================================================================
--- /trunk/archive/scripts/src/phase2/Makefile	(revision 4819)
+++ /trunk/archive/scripts/src/phase2/Makefile	(revision 4820)
@@ -1,9 +1,14 @@
 SHELL = /bin/sh
 CC = gcc
-CFLAGS += -O0 -g -std=c99 -Werror -I/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/include/ -D_GNU_SOURCE
+
+#DEVFLAGS += -DPRODUCTION	# Produce binary as for production (don't use old APIs)
+DEVFLAGS += -DTESTING		# Testing version
+
+CFLAGS += -O0 -g -std=c99 -Werror -I/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/include/ -D_GNU_SOURCE $(DEVFLAGS)
 PSLIB += -L/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/lib/ -lpslib -lgsl -lgslcblas -lfftw3f -lsla -lcfitsio -lm -lxml2 -lmysqlclient
+PSMODULE += -L/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/lib/ -lpsmodule
 LDFLAGS += $(PSLIB)
 
-OBJS = papPhase2.o psAdditionals.o
+OBJS = papPhase2.o psAdditionals.o pmConfig.o pmFPA.o pmFPAConstruct.o papStuff.o
 TARGET = papPhase2
 
Index: /trunk/archive/scripts/src/phase2/papPhase2.c
===================================================================
--- /trunk/archive/scripts/src/phase2/papPhase2.c	(revision 4819)
+++ /trunk/archive/scripts/src/phase2/papPhase2.c	(revision 4820)
@@ -1,5 +1,13 @@
 #include <stdio.h>
+#include <string.h>
+
 #include "pslib.h"
 #include "psAdditionals.h"
+
+#include "pmSubtractBias.h"
+
+#include "pmFPA.h"
+#include "pmConfig.h"
+#include "pmFPAConstruct.h"
 
 // Phase 2 needs to:
@@ -25,4 +33,7 @@
 // 
 // Most are self-explanatory.  "-chip" says "only work on this particular chip".
+
+
+#define RECIPE "PHASE2"			// Name of the recipe to use
 
 static psMemoryId memPrintAlloc(const psMemBlock *mb)
@@ -44,13 +55,25 @@
     return 0;
 }
+static void memPrint(const psPtr ptr)
+{
+    psMemBlock *mb = ((psMemBlock*)ptr) - 1;
+    printf("Memory block %lld (%lld):\n"
+	   "\tFile %s, line %d, size %d\n"
+	   "\tPosts: %x %x %x\n",
+	   mb->id, mb->refCounter, mb->file, mb->lineno, mb->userMemorySize, mb->startblock, mb->endblock,
+	   *(void**)((int8_t *)(mb + 1) + mb->userMemorySize));
+}
 
 int main(int argc, char *argv[])
 {
-    psMemAllocateCallbackSetID(71);
-    psMemFreeCallbackSetID(71);
+#if 0
+    // Hunting memory leaks
+    psMemAllocateCallbackSetID(3539);
+    psMemFreeCallbackSetID(3539);
     psMemAllocateCallbackSet(memPrintAlloc);
     psMemFreeCallbackSet(memPrintFree);
-
-    psTraceSetLevel("psArgumentParse", 10);
+#endif
+
+//    psTraceSetLevel(".", 10);
 
     // Parse the configurations
@@ -58,11 +81,8 @@
     psMetadata *camera = NULL;          // Camera configuration
     psMetadata *recipe = NULL;          // Recipe configuration
-
-#if 0
-    if (! pmConfigRead(&site, &camera, &recipe, &argc, argv, "moduleName")) {
-        psErrorStackPrint("Can't find site configuration!\n");
+    if (! pmConfigRead(&site, &camera, &recipe, &argc, argv, RECIPE)) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
         exit(EXIT_FAILURE);
     }
-#endif
 
     // Parse other command-line arguments
@@ -73,14 +93,4 @@
     psMetadataAddStr(arguments, PS_LIST_TAIL, "-mask", "Name of the mask image", "");
     psMetadataAddS32(arguments, PS_LIST_TAIL, "-chip", "Chip number to process (if positive)", -1);
-
-    psMetadataAdd(arguments, PS_LIST_TAIL, "-string", PS_META_STR, "Test string", "SomeString");
-    psMetadataAdd(arguments, PS_LIST_TAIL, "-bool", PS_META_BOOL, "Test bool", false);
-#if 1
-    psMetadataAdd(arguments, PS_LIST_TAIL, "-int", PS_META_S32 | PS_META_DUPLICATE_OK, "Test integer 1", 1);
-    psMetadataAdd(arguments, PS_LIST_TAIL, "-int", PS_META_S32 | PS_META_DUPLICATE_OK, "Test integer 2", 2);
-    psMetadataAdd(arguments, PS_LIST_TAIL, "-int", PS_META_S32 | PS_META_DUPLICATE_OK, "Test integer 3", 3);
-#endif
-    psMetadataAdd(arguments, PS_LIST_TAIL, "-float", PS_META_F32, "Test float", 1.234567);
-
     if (! psArgumentParse(arguments, &argc, argv) || argc != 3) {
 	printf("\nPan-STARRS Phase 2 processing\n\n");
@@ -92,7 +102,4 @@
     const char *inputName = argv[1];	// Name of input image
     const char *outputName = argv[2];	// Name of output image
-
-    printf("Success: %s %s\n", inputName, outputName);
-
     const char *biasName = psMetadataLookupString(NULL, arguments, "-bias"); // Name of bias image
     const char *darkName = psMetadataLookupString(NULL, arguments, "-dark"); // Name of dark image
@@ -100,4 +107,5 @@
     const char *maskName = psMetadataLookupString(NULL, arguments, "-mask"); // Name of mask image
     const int chipNum = psMetadataLookupS32(NULL, arguments, "-chip"); // Chip number to work on
+    printf("Input: %s\nOutput: %s\n", inputName, outputName);
     printf("Bias: %s\n", biasName);
     printf("Dark: %s\n", darkName);
@@ -105,57 +113,49 @@
     printf("Mask: %s\n", maskName);
     printf("Chip: %d\n", chipNum);
-
-    // For testing
-    psString string = psMetadataLookupString(NULL, arguments, "-string");
-    float floating = psMetadataLookupF32(NULL, arguments, "-float");
-    bool mdok = false;
-    bool boolean = psMetadataLookupBool(&mdok, arguments, "-bool");
-    if (!mdok) printf("Urgh!\n");
-    printf("String: %s\n", string);
-    printf("Float: %f\n", floating);
-    printf("Boolean: %d\n", boolean);
+    psFree(arguments);
+
+    // Open the input
+#ifdef PRODUCTION
+    psFits *inputFile = psFitsOpen(inputName, "r"); // File handle for FITS file
+#else
+    psFits *inputFile = psFitsAlloc(inputName);	// File handle for FITS file
+#endif
+    if (! inputFile) {
+	psErrorStackPrint(stderr, "Can't open input image: %s\n", inputName);
+        exit(EXIT_FAILURE);
+    }
+    psMetadata *header = psFitsReadHeader(NULL, inputFile); // FITS header
+#if PRODUCTION
+    psDB *database = pmConfigDB(site);	// Database handle
+#else
+    psDB *database = NULL;		// Database handle
+#endif
 
 #if 0
-    psMetadataItem *intItem = psMetadataLookup(arguments, "-int");
-    if (intItem->type == PS_META_MULTI) {
-	psList *intMulti = intItem->data.V;
-	psListIterator *intIter = psListIteratorAlloc(intMulti, PS_LIST_HEAD, false);
-	psMetadataItem *intMultiItem = NULL;
-	while (intMultiItem = psListGetAndIncrement(intIter)) {
-	    printf("Integer: %d\n", intMultiItem->data.S32);
-	}
-	psFree(intIter);
-    }
-#endif
-
-
-
-
-
-#if 0
-    // Open the input
-    psFits *inputFile = psFitsOpen(inputName, "r"); // File handle for FITS file
-    if (! inputFile) {
-	psErrorStackPrint("Can't open input image: %s\n", inputName);
-        exit(EXIT_FAILURE);
-    }
-    psMetadata *header = psFitsReadHeader(NULL, inputFile); // FITS header
-    psDB *database = pmConfigDB(site);	// Database handle
-
     // Open the output and output mask
     // We do it here so that we don't process the whole lot and then find out we can't open the output file
+#ifdef PRODUCTION
     psFits *outputFile = psFitsOpen(outputName, "w");
+#else
+    psFits *outputFile = psFitsAlloc(outputName);
+#endif
     if (! outputFile) {
-	psErrorStackPrint("Can't open output image: %s\n", outputName);
+	psErrorStackPrint(stderr, "Can't open output image: %s\n", outputName);
 	exit(EXIT_FAILURE);
     }
     psString outputMaskName = psStringCopy(outputName);
-    (void)psStringAppend(outputMaskName, ".mask");
-    psFits *outputMaskFile = psFits(outputMaskName, "w");
+    (void)psStringAppend(&outputMaskName, ".mask");
+#ifdef PRODUCTION
+    psFits *outputMaskFile = psFitsOpen(outputMaskName, "w");
+#else
+    psFits *outputMaskFile = psFitsAlloc(outputMaskName);
+#endif
     if (! outputMaskFile) {
-	psErrorStackPrint("Can't open output mask image: %s\n", outputMaskName);
+	psErrorStackPrint(stderr, "Can't open output mask image: %s\n", outputMaskName);
 	exit(EXIT_FAILURE);
     }
     psFree(outputMaskName);
+#endif
+
 	
     // Get camera configuration from header if not already defined
@@ -163,22 +163,22 @@
 	camera = pmConfigCameraFromHeader(site, header);
 	if (! camera) {
-	    psErrorStackPrint("Can't find camera configuration!\n");
-	    exit(EXIT_FAILURE);
-	}
-    } else if (! pmConfigValidateCamera(camera, inputHeader)) {
-	psError("phase2", true, "%s does not seem to be from the camera.\n", inputName);
+	    psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+	    exit(EXIT_FAILURE);
+	}
+   } else if (! pmConfigValidateCamera(camera, header)) {
+	psError(PS_ERR_IO, true, "%s does not seem to be from the camera.\n", inputName);
 	exit(EXIT_FAILURE);
     }
-    if (! recipe && !(recipe = pmConfigRecipeFromCamera(camera, "moduleName"))) {
-        psErrorStackPrint("Can't find recipe configuration!\n");
+    if (! recipe && !(recipe = pmConfigRecipeFromCamera(camera, RECIPE))) {
+        psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
         exit(EXIT_FAILURE);
     }
 
     // Construct camera in preparation for reading
-    pmFPA *input = pmFPAConstruct(camera, db);
-    pmFPA *mask = pmFPAConstruct(camera, db);
-    pmFPA *bias = pmFPAConstruct(camera, db);
-    pmFPA *dark = pmFPAConstruct(camera, db);
-    pmFPA *flat = pmFPAConstruct(camera, db);
+    pmFPA *input = pmFPAConstruct(camera, database);
+    pmFPA *mask = pmFPAConstruct(camera, database);
+    pmFPA *bias = pmFPAConstruct(camera, database);
+    pmFPA *dark = pmFPAConstruct(camera, database);
+    pmFPA *flat = pmFPAConstruct(camera, database);
 
     // Set various tasks
@@ -236,5 +236,4 @@
 		     " assuming NONE.\n", mode);
 	}
-	psFree(mode);
 	psString fit = psMetadataLookupString(NULL, recipe, "OVERSCAN.FIT");
 	if (strcasecmp(fit, "POLYNOMIAL") == 0) {
@@ -246,8 +245,7 @@
 		     " assuming NONE.\n", fit);
 	}
-	psFree(fit);
 	overscanBins = psMetadataLookupS32(NULL, recipe, "OVERSCAN.BIN");
 	if (overscanBins <= 0) {
-	    psErrorStackPrint("OVERSCAN.BIN (%d) is non-positive --- assuming 1.\n", overscanBins);
+	    psErrorStackPrint(stderr, "OVERSCAN.BIN (%d) is non-positive --- assuming 1.\n", overscanBins);
 	    overscanBins = 1;
 	}
@@ -258,5 +256,5 @@
 	    overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
 	} else {
-	    psErrorStackPrint("OVERSCAN.STAT (%s) is not one of MEAN, MEDIAN: assuming MEAN\n", stat);
+	    psErrorStackPrint(stderr, "OVERSCAN.STAT (%s) is not one of MEAN, MEDIAN: assuming MEAN\n", stat);
 	    overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);	    
 	}
@@ -270,5 +268,6 @@
 	}
     }
-	 
+
+#if 0
     // Chip selection
     if (chipNum >= 0) {
@@ -276,5 +275,5 @@
 	    ! pmFPASelectChip(dark, chipNum) || ! pmFPASelectChip(flat, chipNum) ||
 	    ! pmFPASelectChip(mask, chipNum)) {
-	    psErrorStackPrint("Chip number %d doesn't exist in camera.\n", chipNum);
+	    psErrorStackPrint(stderr, "Chip number %d doesn't exist in camera.\n", chipNum);
 	    exit(EXIT_FAILURE);
 	}
@@ -284,8 +283,13 @@
     // Read in the input pixels
     if (! pmFPARead(input, inputFile)) {
-	psErrorStackPrint("Unable to populate camera from FITS file: %s\n", inputName);
+	psErrorStackPrint(stderr, "Unable to populate camera from FITS file: %s\n", inputName);
 	exit(EXIT_FAILURE);
     }
+#ifdef PRODUCTION
     psFitsClose(inputFile);
+#else
+    psFree(inputFile);
+#endif
+
 
     // Load the calibration frames, if required
@@ -299,5 +303,5 @@
 	psFree(biasHeader);
 	if (! pmFPARead(bias, biasFile)) {
-	    psErrorStackPrint("Unable to populate bias camera from fits FITS: %s\n", biasName);
+	    psErrorStackPrint(stderr, "Unable to populate bias camera from fits FITS: %s\n", biasName);
 	    exit(EXIT_FAILURE);
 	}
@@ -314,5 +318,5 @@
 	psFree(darkHeader);
 	if (! pmFPARead(dark, darkFile)) {
-	    psErrorStackPrint("Unable to populate dark camera from fits FITS: %s\n", darkName);
+	    psErrorStackPrint(stderr, "Unable to populate dark camera from fits FITS: %s\n", darkName);
 	    exit(EXIT_FAILURE);
 	}
@@ -329,5 +333,5 @@
 	psFree(maskHeader);
 	if (! pmFPARead(mask, maskFile)) {
-	    psErrorStackPrint("Unable to populate mask camera from fits FITS: %s\n", maskName);
+	    psErrorStackPrint(stderr, "Unable to populate mask camera from fits FITS: %s\n", maskName);
 	    exit(EXIT_FAILURE);
 	}
@@ -344,5 +348,5 @@
 	psFree(flatHeader);
 	if (! pmFPARead(flat, flatFile)) {
-	    psErrorStackPrint("Unable to populate flat camera from fits FITS: %s\n", flatName);
+	    psErrorStackPrint(stderr, "Unable to populate flat camera from fits FITS: %s\n", flatName);
 	    exit(EXIT_FAILURE);
 	}
@@ -414,5 +418,5 @@
 		darkTime = pmReadoutGetDarkTime(darkReadout);
 		if (darkTime <= 0.0) {
-		    psErrorStackPrint("DARKTIME for dark image (%f) is non-positive.\n", darkTime);
+		    psErrorStackPrint(stderr, "DARKTIME for dark image (%f) is non-positive.\n", darkTime);
 		    exit(EXIT_FAILURE);
 		}
@@ -469,6 +473,6 @@
 			    psLookupTable *table = psLookupTableAlloc(name, "%f %f", 0);
 			    if (psLookupTableRead(table) <= 0) {
-				psErrorStackPrint("Unable to read non-linearity correction file %s --- "
-						  "ignored\n", tableName);
+				psErrorStackPrint(stderr, "Unable to read non-linearity correction file %s "
+						  "--- ignored\n", tableName);
 			    } else {
 				(void)pmNonLinearityLookup(inputReadout, table);
@@ -523,6 +527,6 @@
 					psLookupTable *table = psLookupTableAlloc(tableName, "%f %f", 0);
 					if ((int numLines = psLookupTableRead(table)) <= 0) {
-					    psErrorStackPrint("Unable to read non-linearity correction "
-							      "file %s --- ignored\n", tableName);
+					    psErrorStackPrint(stderr, "Unable to read non-linearity "
+							      "correction file %s --- ignored\n", tableName);
 					} else {
 					    (void)pmNonLinearityLookup(inputReadout, table);
@@ -553,5 +557,6 @@
 		    float inputTime = pmReadoutGetDarkTime(inputReadout); // Dark time for input image
 		    if (inputTime <= 0) {
-			psErrorStackPrint("DARKTIME for input image (%f) is non-positive.\n", inputTime);
+			psErrorStackPrint(stderr, "DARKTIME for input image (%f) is non-positive.\n",
+					  inputTime);
 			exit(EXIT_FAILURE);
 		    }
@@ -583,5 +588,6 @@
 			overscanMode = PM_OVERSCAN_COLUMNS;
 		    } else {
-			psErrorStackPrint("CELL.READDIR (%d) is not 1 or 2 --- assuming 1.\n", readdir);
+			psErrorStackPrint(stderr, "CELL.READDIR (%d) is not 1 or 2 --- assuming 1.\n",
+					  readdir);
 			overscanMode = PM_OVERSCAN_ROWS;
 		    }
@@ -655,7 +661,18 @@
     pmFPAWrite(outputFile, input);
     pmFPAWriteMask(outputMaskFile, input);
+#ifdef PRODUCTION
     psFitsClose(outputFile);
     psFitsClose(outputMaskFile);
-
+#else
+    psFree(outputFile);
+    psFree(outputMaskFile);
+#endif
+
+#endif
+
+    psFree(site);
+    psFree(header);
+    psFree(camera);
+    psFree(recipe);
     psFree(input);
     psFree(mask);
@@ -663,18 +680,21 @@
     psFree(dark);
     psFree(flat);
-
-#endif
-
-    
+    psFree(overscanStats);
+    psFree(inputFile);
+
+#if 1
     psMemCheckCorruption(true);
-    psFree(arguments);
     psMemBlock **leaks = NULL;          // List of leaks
     int nLeaks = psMemCheckLeaks(0, &leaks, NULL, false); // Number of leaks
     printf("%d leaks found.\n", nLeaks);
+#if 1
     for (int i = 0; i < nLeaks; i++) {
-        printf("Memory leak detection: memBlock %lld\n"
+        printf("Memory leak detection: memBlock %lld (%lld)\n"
 	       "\tFile %s, line %d, size %d\n",
-	       leaks[i]->id, leaks[i]->file, leaks[i]->lineno, leaks[i]->userMemorySize);
-    }
+	       leaks[i]->id, leaks[i]->refCounter, leaks[i]->file, leaks[i]->lineno,
+	       leaks[i]->userMemorySize);
+    }
+#endif
+#endif
 
 }
Index: /trunk/archive/scripts/src/phase2/papStuff.c
===================================================================
--- /trunk/archive/scripts/src/phase2/papStuff.c	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/papStuff.c	(revision 4820)
@@ -0,0 +1,43 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "pslib.h"
+#include "psAdditionals.h"
+
+#include "papStuff.h"
+
+// Split string on given characters
+psList *papSplit(const char *string, const char *splitters)
+{
+    psList *values = psListAlloc(NULL);	// The list of values to return
+    unsigned int length = strlen(string); // The length of the string
+    unsigned int numSplitters = strlen(splitters); // Number of characters that might split
+    unsigned int start = 0;		// The position of the start of a word
+    for (int i = 1; i < length; i++) {
+	bool split = false;		// Is this character a splitter?
+	for (int j = 0; j < numSplitters && ! split; j++) {
+	    if (string[i] == splitters[j]) {
+		split = true;
+	    }
+	}
+	if (split) {
+	    if (i == start) {
+		// Some idiot put in two spaces, or two commas or something
+		start++;
+	    } else {
+		// We're at the end of the word
+		psString word = psStringNCopy(&string[start], i - start);
+		(void)psListAdd(values, PS_LIST_TAIL, word);
+		start = i + 1;
+	    }
+	}
+    }
+    if (start < length) {
+	// Copy the last word
+	psString word = psStringNCopy(&string[start], length - start);
+	(void)psListAdd(values, PS_LIST_TAIL, word);
+    }
+
+    return values;
+}
+
Index: /trunk/archive/scripts/src/phase2/papStuff.h
===================================================================
--- /trunk/archive/scripts/src/phase2/papStuff.h	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/papStuff.h	(revision 4820)
@@ -0,0 +1,9 @@
+#ifndef PAP_STUFF_H
+#define PAP_STUFF_H
+
+#include "pslib.h"
+
+// Split string on given characters
+psList *papSplit(const char *string, const char *splitters);
+
+#endif
Index: /trunk/archive/scripts/src/phase2/pmConfig.c
===================================================================
--- /trunk/archive/scripts/src/phase2/pmConfig.c	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/pmConfig.c	(revision 4820)
@@ -0,0 +1,307 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "pslib.h"
+#include "psAdditionals.h"
+#include "pmConfig.h"
+
+#define PS_SITE "PS_SITE"		// Name of the environment variable containing the site config file
+#define DEFAULT_SITE "ipprc.config"	// Default site config file
+
+static bool readConfig(psMetadata **config, // Config to output
+		       const char *name,// Name of file
+		       const char *description // Description of file
+    )
+{
+    int numBadLines = 0;		// Number of bad lines in config file
+    psLogMsg(__func__, PS_LOG_INFO, "Loading %s configuration from file %s\n", description, name);
+    *config = psMetadataConfigParse(NULL, &numBadLines, name, true);
+    if (numBadLines > 0) {
+	psLogMsg(__func__, PS_LOG_WARN, "%d bad lines in %s configuration file (%s)\n", description,
+		 name);
+    }
+    if (! *config) {
+	psError(PS_ERR_IO, false, "Unable to read %s configuration from %s\n", description, name);
+	return false;
+    }
+
+    return true;
+}
+
+
+bool pmConfigRead(psMetadata **site, psMetadata **camera, psMetadata **recipe,
+                  int *argc, char **argv, const char *recipeName)
+{
+    // Make sure we've been given the correct inputs, and that we won't leak memory
+    assert(site && *site == NULL);
+    assert(camera && *camera == NULL);
+    assert(recipe && *recipe == NULL);
+    assert(*argc > 0);
+    assert(argv);
+
+    char *siteName = NULL;		// Name of the site configuration file
+
+    // First, try command line
+    int argNum = 0;			// Number of the site argument
+    if (argNum = psArgumentGet(*argc, argv, "-site")) {
+	(void)psArgumentRemove(argNum, argc, argv);
+	if (argNum >= *argc) {
+	    psLogMsg(__func__, PS_LOG_WARN,
+		     "-site command-line switch provided without the required filename --- ignored.\n");
+	} else {
+	    siteName = argv[argNum];
+	    (void)psArgumentRemove(argNum, argc, argv);
+	}
+    }
+    // Next, try environment variable
+    if (! siteName) {
+	siteName = getenv(PS_SITE);
+    }
+    // Last chance is ~/.ipprc
+    bool cleanupSiteName = false;	// Do I have to psFree siteName?
+    if (! siteName) {
+	siteName = psStringCopy(DEFAULT_SITE);
+	cleanupSiteName = true;
+    }
+
+    if (! readConfig(site, siteName, "site")) {
+	if (cleanupSiteName) {
+	    psFree(siteName);
+	}
+	return false;
+    }
+
+    // Next is the camera configuration
+    if (argNum = psArgumentGet(*argc, argv, "-camera")) {
+	(void)psArgumentRemove(argNum, argc, argv);
+	if (argNum >= *argc) {
+	    psLogMsg(__func__, PS_LOG_WARN,
+		     "-camera command-line switch provided without the required filename --- ignored.\n");
+	} else {
+	    (void)psArgumentRemove(argNum, argc, argv);
+	    (void)readConfig(camera, argv[argNum], "camera");
+	}
+    }
+
+    // And then the recipe configuration
+    if (argNum = psArgumentGet(*argc, argv, "-recipe")) {
+	(void)psArgumentRemove(argNum, argc, argv);
+	if (argNum >= *argc) {
+	    psLogMsg(__func__, PS_LOG_WARN,
+		     "-recipe command-line switch provided without the required filename --- ignored.\n");
+	} else {
+	    (void)psArgumentRemove(argNum, argc, argv);
+	    (void)readConfig(recipe, argv[argNum], "recipe");
+	}
+    }
+    // Or, load the recipe from the camera file, if appropriate
+    if (! *recipe && *camera && recipeName) {
+	*recipe = pmConfigRecipeFromCamera(*camera, recipeName);
+    }
+
+
+    // Now we can look into the site configuration and do the required stuff
+    bool mdok = true;			// Status of MD lookup result
+    psString timeName = psMetadataLookupString(&mdok, *site, "TIME"); // Name of time file
+    if (mdok && timeName) {
+	psTrace(__func__, 7, "Initialising psTime with file %s\n", timeName);
+#ifdef PRODUCTION
+	psTimeInitialize(timeName);
+#else
+	psLibInit(timeName);
+#endif
+    }
+
+    int logLevel = psMetadataLookupS32(&mdok, *site, "LOGLEVEL"); // Logging level
+    if (mdok && logLevel >= 0) {
+	psTrace(__func__, 7, "Setting log level to %d\n", logLevel);
+	psLogSetLevel(logLevel);
+    }
+
+    psString logFormat = psMetadataLookupString(&mdok, *site, "LOGLEVEL"); // Log format
+    if (mdok && logFormat) {
+	psTrace(__func__, 7, "Setting log format to %s\n", logFormat);
+	psLogSetFormat(logFormat);
+    }
+
+    psString logDest = psMetadataLookupString(&mdok, *site, "LOGDEST"); // Log destination
+    if (mdok && logDest) {
+	// XXX: Only stdout is provided for now; this section should be expanded in the future to do files,
+	// and perhaps even sockets.
+	if (strcasecmp(logDest, "STDOUT") != 0) {
+	    psLogMsg(__func__, PS_LOG_WARN, "Only STDOUT is currently supported as a log destination.\n");
+	}
+	psTrace(__func__, 7, "Setting log destination to STDOUT.\n");
+	psLogSetDestination(0);
+    }
+
+    psMetadata *trace = psMetadataLookupMD(&mdok, *site, "TRACE"); // Trace levels
+    if (mdok && trace) {
+	psMetadataIterator *traceIter = psMetadataIteratorAlloc(trace, PS_LIST_HEAD, NULL); // Iterator
+	psMetadataItem *traceItem = NULL; // Item from MD iteration
+	while (traceItem = psMetadataGetAndIncrement(traceIter)) {
+	    if (traceItem->type != PS_META_S32) {
+		psLogMsg(__func__, PS_LOG_WARN, "The level for trace component %s is not of type S32 (%x)\n",
+			 traceItem->name, traceItem->type);
+		continue;
+	    }
+	    psTrace(__func__, 7, "Setting trace level for %s to %d\n", traceItem->name, traceItem->data.S32);
+	    (void)psTraceSetLevel(traceItem->name, traceItem->data.S32);
+	}
+	psFree(traceIter);
+    }
+
+    if (cleanupSiteName) {
+	psFree(siteName);
+    }
+    return true;
+}
+
+bool pmConfigValidateCamera(const psMetadata *camera, const psMetadata *header)
+{
+    // Read the rule for that camera
+    bool mdStatus = true;		// Status of MD lookup
+    psMetadata *rule = psMetadataLookupMD(&mdStatus, camera, "RULE");
+    if (! mdStatus || ! rule) {
+	psLogMsg(__func__, PS_LOG_WARN, "Unable to read rule for camera.\n");
+	return false;
+    }
+
+    // Apply the rules
+    psMetadataIterator *ruleIter = psMetadataIteratorAlloc(rule, PS_LIST_HEAD, NULL); // Rule iterator
+    psMetadataItem *ruleItem = NULL;	// Item from the metadata
+    bool match = true;			// Does it match?
+    while ((ruleItem = psMetadataGetAndIncrement(ruleIter)) && match) {
+	// Check for the existence of the rule
+	psMetadataItem *headerItem = psMetadataLookup((psMetadata*)header, ruleItem->name);
+	if (! headerItem || headerItem->type != ruleItem->type) {
+	    match = false;
+	}
+
+	// Check to see if the rule works
+	switch (ruleItem->type) {
+	  case PS_META_STR:
+	    psTrace(__func__, 8, "Matching %s: '%s' vs '%s'\n", ruleItem->name,
+		    ruleItem->data.V, headerItem->data.V);
+	    if (strncmp(ruleItem->data.V, headerItem->data.V,
+			    strlen(ruleItem->data.V)) != 0) {
+		match = false;
+	    }
+	    break;
+	  case PS_TYPE_S32:
+	  case PS_TYPE_BOOL:
+	    psTrace(__func__, 8, "Matching %s: %d vs %d\n", ruleItem->name,
+		    ruleItem->data.S32, headerItem->data.S32);
+	    if (ruleItem->data.S32 != headerItem->data.S32) {
+		match = false;
+	    }
+	    break;
+	  case PS_TYPE_F32:
+	    psTrace(__func__, 8, "Matching %s: %f vs %f\n", ruleItem->name,
+		    ruleItem->data.F32, headerItem->data.F32);
+	    if (ruleItem->data.F32 != headerItem->data.F32) {
+		match = false;
+	    }
+	    break;
+	  case PS_TYPE_F64:
+	    psTrace(__func__, 8, "Matching %s: %g vs %g\n", ruleItem->name,
+		    ruleItem->data.F64, headerItem->data.F64);
+	    if (ruleItem->data.F64 != headerItem->data.F64) {
+		match = false;
+	    }
+	    break;
+	  default:
+	    psLogMsg(__func__, PS_LOG_WARN, "Ignoring invalid type in metadata: %x\n",
+		     ruleItem->type);
+	}
+    } // Iterating through the RULEs
+
+    psFree(ruleIter);
+
+    return match;
+}
+    
+
+
+// Work out what camera we have, based on the FITS header and a set of rules specified in the IPP
+// configuration; return the camera configuration
+psMetadata *pmConfigCameraFromHeader(const psMetadata *ipprc, // The IPP configuration
+				     const psMetadata *header // The FITS header
+    )
+{
+    bool mdStatus = false;		// Metadata lookup status
+    psMetadata *cameras = psMetadataLookupMD(&mdStatus, ipprc, "CAMERAS");
+    if (! mdStatus) {
+	psError(PS_ERR_IO, false, "Unable to find CAMERAS in the configuration.\n");
+	return NULL;
+    }
+
+    psMetadata *winner = NULL;	      // The camera configuration whose rule first matches the supplied header
+
+    // Iterate over the cameras
+    psMetadataIterator *iterator = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // MD Iterator
+    psMetadataItem *cameraItem = NULL; // Item from the metadata
+    while (cameraItem = psMetadataGetAndIncrement(iterator)) {
+	// Open the camera information
+	psTrace(__func__, 3, "Inspecting camera %s (%s)\n", cameraItem->name,
+		cameraItem->comment);
+	psMetadata *camera = NULL;	// The camera metadata
+	if (cameraItem->type == PS_META_META) {
+	    camera = psMemIncrRefCounter(cameraItem->data.md);
+	} else if (cameraItem->type == PS_META_STR) {
+	    psTrace(__func__, 5, "Reading camera configuration for %s...\n", cameraItem->name);
+	    int badLines = 0;		// Number of bad lines in reading camera configuration
+	    camera = psMetadataConfigParse(NULL, &badLines, cameraItem->data.V, true);
+	    if (badLines > 0) {
+		psLogMsg(__func__, PS_LOG_WARN, "%d bad lines encountered while reading camera"
+			 "configuration %s\n", badLines, cameraItem->name);
+	    }
+	}
+
+	if (! camera) {
+	    psLogMsg(__func__, PS_LOG_WARN, "Unable to interpret camera configuration for %s (%s)\n",
+		     cameraItem->name, cameraItem->comment);
+	    continue;
+	}
+
+	if (pmConfigValidateCamera(camera, header)) {
+	    if (! winner) {
+		// This is the first match
+		winner = psMemIncrRefCounter(camera);
+		psLogMsg(__func__, PS_LOG_INFO, "FITS header matches camera %s\n",
+			 cameraItem->name);
+	    } else {
+		// We have a duplicate match
+		psLogMsg(__func__, PS_LOG_WARN, "Additional camera found that matches the rules: %s\n",
+			 cameraItem->name);
+	    }
+	} // Done inspecting the camera
+
+	psFree(camera);
+	
+    } // Done looking at all cameras
+    if (! winner) {
+	psError(PS_ERR_IO, true, "Unable to find an camera that matches input FITS header!\n");
+    }
+
+    psFree(iterator);
+    return winner;
+}
+
+psMetadata *pmConfigRecipeFromCamera(const psMetadata *camera, const char *recipeName)
+{
+    assert(camera);
+    assert(recipeName);
+
+    psMetadata *recipe = NULL;	// Recipe to read
+    bool mdok = true;			// Status of MD lookup
+    psMetadata *recipes = psMetadataLookupMD(&mdok, camera, "RECIPES"); // The list of recipes
+    if (! mdok || ! recipes) {
+	psLogMsg(__func__, PS_LOG_WARN, "RECIPES in the camera configuration file is not of type METADATA\n");
+    } else {
+	psString recipeFileName = psMetadataLookupString(&mdok, recipes, recipeName);
+	(void)readConfig(&recipe, recipeFileName, "recipe");
+    }
+
+    return recipe;
+}
Index: /trunk/archive/scripts/src/phase2/pmConfig.h
===================================================================
--- /trunk/archive/scripts/src/phase2/pmConfig.h	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/pmConfig.h	(revision 4820)
@@ -0,0 +1,12 @@
+#ifndef PM_CONFIG_H
+#define PM_CONFIG_H
+
+#include "pslib.h"
+
+bool pmConfigRead(psMetadata **site, psMetadata **camera, psMetadata **recipe,
+                  int *argc, char **argv, const char *recipeName);
+bool pmConfigValidateCamera(const psMetadata *camera, const psMetadata *header);
+psMetadata *pmConfigCameraFromHeader(const psMetadata *site, const psMetadata *header);
+psMetadata *pmConfigRecipeFromCamera(const psMetadata *camera, const char *recipeName);
+
+#endif
Index: /trunk/archive/scripts/src/phase2/pmFPA.c
===================================================================
--- /trunk/archive/scripts/src/phase2/pmFPA.c	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/pmFPA.c	(revision 4820)
@@ -0,0 +1,182 @@
+#include <stdio.h>
+#include <string.h>
+#include "pslib.h"
+#include "psAdditionals.h"
+
+#include "pmFPA.h"
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Allocators
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmFPA *pmFPAAlloc(const psMetadata *camera, // Camera configuration
+		  psDB *db		// Database
+    )
+{
+    pmFPA *fpa = psAlloc(sizeof(pmFPA));// The FPA
+    psMemSetDeallocator(fpa, (psFreeFcn)p_pmFPAFree);
+
+    // Fill in the components
+    fpa->fromTangentPlane = NULL;
+    fpa->toTangentPlane = NULL;
+    fpa->projection = NULL;
+
+    fpa->values = psMetadataAlloc();
+//    fpa->camera = psMemIncrRefCounter((psPtr)camera);
+    fpa->db = db;
+    fpa->chips = psArrayAlloc(0);
+
+    fpa->extname = NULL;
+    fpa->pixels = NULL;
+    fpa->header = NULL;
+
+    return fpa;
+}
+
+void p_pmFPAFree(pmFPA *fpa)
+{
+    psFree(fpa->fromTangentPlane);
+    psFree(fpa->toTangentPlane);
+    psFree(fpa->projection);
+
+    psFree(fpa->values);
+    psFree((psPtr)fpa->camera);
+    psFree(fpa->db);
+    psFree(fpa->chips);
+
+    psFree(fpa->pixels);
+    psFree(fpa->header);
+}
+
+pmChip *pmChipAlloc(pmFPA *fpa,	// FPA to which the chip belongs
+		    psString name	// Chip name
+    )
+{
+    pmChip *chip = psAlloc(sizeof(pmChip)); // The chip
+    psMemSetDeallocator(chip, (psFreeFcn)p_pmChipFree);
+
+    // Push onto the array of chips
+    fpa->chips = psArrayAdd(fpa->chips, 0, chip);
+
+    // Fill in the components
+    *(int*)&chip->col0 = 0;		// Good enough for now
+    *(int*)&chip->row0 = 0;		// Good enough for now
+
+    chip->toFPA = NULL;
+    chip->fromFPA = NULL;
+
+    chip->values = psMetadataAlloc();
+//    chip->parent = psMemIncrRefCounter(fpa);
+    chip->cells = psArrayAlloc(0);
+
+    chip->extname = NULL;
+    chip->pixels = NULL;
+    chip->header = NULL;
+
+    psMetadataAddStr(chip->values, PS_LIST_HEAD, "CHIP.NAME", "Chip name added at pmChipAlloc", name);
+
+    return chip;
+}
+
+void p_pmChipFree(pmChip *chip)
+{
+    psFree(chip->toFPA);
+    psFree(chip->fromFPA);
+
+    psFree(chip->values);
+    psFree(chip->cells);
+//    psFree(chip->parent);
+
+    psFree(chip->pixels);
+    psFree(chip->header);
+}
+
+pmCell *pmCellAlloc(pmChip *chip,	// Chip to which the cell belongs
+		    psMetadata *values,	// Initial values for concepts
+		    psString name	// Name of cell
+    )
+{
+    pmCell *cell = psAlloc(sizeof(pmCell)); // The cell
+    psMemSetDeallocator(cell, (psFreeFcn)p_pmCellFree);
+
+    // Push onto the array of chips
+    chip->cells = psArrayAdd(chip->cells, 0, cell);
+
+    // Fill in components
+    *(int*)&cell->col0 = 0;		// Good enough for now
+    *(int*)&cell->row0 = 0;		// Good enough for now
+
+    cell->toChip = NULL;
+    cell->fromChip = NULL;
+    cell->toFPA = NULL;
+    cell->toTP = NULL;
+    cell->toSky = NULL;
+
+    cell->values = psMemIncrRefCounter(values);
+    cell->readouts = psArrayAlloc(0);
+//    cell->parent = psMemIncrRefCounter(chip);
+
+    cell->extname = NULL;
+    cell->pixels = NULL;
+    cell->header = NULL;
+
+    psMetadataAddStr(cell->values, PS_LIST_HEAD, "CELL.NAME", "Cell name added at pmCellAlloc", name);
+
+    return cell;
+}
+
+void p_pmCellFree(pmCell *cell)
+{
+    psFree(cell->toChip);
+    psFree(cell->fromChip);
+    psFree(cell->toFPA);
+    psFree(cell->toTP);
+    psFree(cell->toSky);
+
+    psFree(cell->values);
+    psFree(cell->readouts);
+//    psFree(cell->parent);
+
+    psFree(cell->pixels);
+    psFree(cell->header);
+}
+
+pmReadout *pmReadoutAlloc(pmCell *cell, // Cell to which the readout belongs
+			  int readoutNum, // Number of the readout
+			  psImage *image, // The pixels
+			  psList *overscans, // The overscan images
+			  int col0, int row0, int colParity, int rowParity, int colBin, int rowBin // Data
+    )
+{
+    pmReadout *readout = psAlloc(sizeof(pmReadout));
+    psMemSetDeallocator(readout, (psFreeFcn)p_pmReadoutFree);
+    psArray *readouts = cell->readouts;
+    if (readoutNum >= readouts->nalloc) {
+	readouts = psArrayRealloc(readouts, readoutNum);
+	cell->readouts = readouts;
+    }
+    readouts->data[readoutNum] = readout;
+    
+    // Set the components
+    readout->image = image;
+    readout->overscans = overscans;
+    
+    readout->values = psMetadataAlloc();
+    
+    *(int*)&readout->col0 = col0;
+    *(int*)&readout->row0 = row0;
+    *(int*)&readout->colParity = colParity;
+    *(int*)&readout->rowParity = rowParity;
+    *(int*)&readout->colBins = colBin;
+    *(int*)&readout->rowBins = rowBin;
+
+    return readout;
+}
+
+void p_pmReadoutFree(pmReadout *readout)
+{
+    psFree(readout->image);
+    psFree(readout->overscans);
+    psFree(readout->values);
+}
Index: /trunk/archive/scripts/src/phase2/pmFPA.h
===================================================================
--- /trunk/archive/scripts/src/phase2/pmFPA.h	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/pmFPA.h	(revision 4820)
@@ -0,0 +1,104 @@
+#ifndef PM_FPA_H
+#define PM_FPA_H
+
+#include "pslib.h"
+#include "psAdditionals.h"
+
+// Temporary metadata types
+#define PS_META_CHIP PS_META_UNKNOWN
+#define PS_META_CELL PS_META_UNKNOWN
+
+typedef struct {
+    // Astrometric transformations
+    psPlaneDistort* fromTangentPlane;	// Transformation from tangent plane to focal plane
+    psPlaneDistort* toTangentPlane;	// Transformation from focal plane to tangent plane
+    psProjection *projection;		// Projection from tangent plane to sky
+    // Information
+    psMetadata *values;			// Important values (cached)
+    const psMetadata *camera;		// Camera configuration
+    psDB *db;				// Database
+    psArray *chips;			// The chips
+    // FITS data
+    const char *extname;		// Extension name, if it corresponds to this level
+    psArray *pixels;			// The pixel data, if it corresponds to this level
+    psMetadata *header;			// The FITS header, if it corresponds to this level
+} pmFPA;
+
+typedef struct {
+    // Offset specifying position on focal plane
+    const int col0;			// Offset from the left of FPA
+    const int row0;			// Offset from the bottom of FPA
+    // Astrometric transformations
+    psPlaneTransform* toFPA;		// Transformation from chip to FPA coordinates
+    psPlaneTransform* fromFPA;		// Transformation from FPA to chip coordinates
+    // Information
+    psMetadata *values;			// Important values (cached)
+    psArray *cells;			// The cells (referred to by name)
+    pmFPA *parent;			// Parent FPA
+    // FITS data
+    const char *extname;		// Extension name, if it corresponds to this level
+    psArray *pixels;			// The pixel data, if it corresponds to this level
+    psMetadata *header;			// The FITS header, if it corresponds to this level
+} pmChip;
+
+typedef struct {
+    // Offset specifying position on chip
+    const int col0;			// Offset from the left of chip
+    const int row0;			// Offset from the bottom of chip
+    // Astrometric transformations
+    psPlaneTransform* toChip;		// Transformations from cell to chip coordinates
+    psPlaneTransform* fromChip;		// Transformations from cell to chip coordinates
+    psPlaneTransform* toFPA;		// Transformations from cell to FPA coordinates
+    psPlaneTransform* toTP;		// Transformations from cell to FPA coordinates
+    psPlaneTransform* toSky;		// Transformations from cell to tangent plane coordinates
+    // Information
+    psMetadata *values;			// Important values (cached)
+    psArray *readouts;			// The readouts (referred to by number)
+    pmChip *parent;			// Parent chip
+    // FITS data
+    const char *extname;		// Extension name, if it corresponds to this level
+    psArray *pixels;			// The pixel data, if it corresponds to this level
+    psMetadata *header;			// The FITS header, if it corresponds to this level
+} pmCell;
+
+
+typedef struct {
+    // Details for position on the cell
+    const int col0;			// Offset from the left of cell.
+    const int row0;			// Offset from the bottom of cell.
+    const int colParity;		// Readout Direction X
+    const int rowParity;		// Readout Direction Y
+    const unsigned int colBins;		// Amount of binning in x-dimension
+    const unsigned int rowBins;		// Amount of binning in y-dimension
+    // Information
+    psImage *image;			// The pixels
+    psList *overscans;			// Array of subimages containing the overscan region(s)
+    psMetadata *values;			// readout-level metadata
+} pmReadout;
+
+// Allocators and deallocators
+pmFPA *pmFPAAlloc(const psMetadata *camera, // Camera configuration
+		  psDB *db		// Database
+    );
+void p_pmFPAFree(pmFPA *fpa);
+
+pmChip *pmChipAlloc(pmFPA *fpa,	// FPA to which the chip belongs
+		    psString name	// Name of chip
+    );
+void p_pmChipFree(pmChip *chip);
+
+pmCell *pmCellAlloc(pmChip *chip,	// Chip to which the cell belongs
+		    psMetadata *values,	// Initial values for concepts
+		    psString name	// Name of cell
+    );
+void p_pmCellFree(pmCell *cell);
+
+pmReadout *pmReadoutAlloc(pmCell *cell,	// Cell to which the readout belongs
+			  int readoutNum, // Number of the readout
+			  psImage *image, // The pixels
+			  psList *overscans, // The overscan images
+			  int col0, int row0, int colParity, int rowParity, int colBin, int rowBin // Data
+    );
+void p_pmReadoutFree(pmReadout *readout);
+
+#endif
Index: /trunk/archive/scripts/src/phase2/pmFPAConstruct.c
===================================================================
--- /trunk/archive/scripts/src/phase2/pmFPAConstruct.c	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/pmFPAConstruct.c	(revision 4820)
@@ -0,0 +1,302 @@
+#include <stdio.h>
+#include <string.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmFPAConstruct.h"
+
+#include "papStuff.h"
+
+// NOTE: Need to deal with header inheritance
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read data for a particular cell from the camera configuration
+static psMetadata *getCellData(const psMetadata *camera, // The camera configuration
+			       const char *cellName // The name of the cell
+    )
+{
+    bool status = true;			// Result of MD lookup
+    psMetadata *cells = psMetadataLookupMD(&status, camera, "CELLS"); // The CELLS
+    if (! status) {
+	psError(PS_ERR_IO, false, "Unable to determine CELLS of camera.\n");
+	return NULL;
+    }
+
+    psMetadata *cellData = psMetadataLookupMD(&status, cells, cellName); // The data for the particular cell
+    if (! status) {
+	psError(PS_ERR_IO, false, "Unable to find specs for cell %s: ignored\n", cellName);
+    }
+    return cellData;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmFPA *pmFPAConstruct(const psMetadata *camera, // The camera configuration
+		      psDB *db		// Database handle
+    )
+{
+    pmFPA *fpa = pmFPAAlloc(camera, db); // The FPA to fill out
+    bool mdStatus = true;		// Status from metadata lookups
+    const char *phuType = psMetadataLookupString(&mdStatus, camera, "PHU"); // What is the PHU?
+    const char *extType = psMetadataLookupString(&mdStatus, camera, "EXTENSIONS"); // What's in the extns?
+
+
+    if (strcasecmp(phuType, "FPA") == 0) {
+	// The FITS file contains an entire FPA
+
+	psMetadata *contents = psMetadataLookupMD(&mdStatus, camera, "CONTENTS"); // The CONTENTS
+	if (! mdStatus) {
+	    psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
+	    psFree(fpa);
+	    return NULL;
+	}
+
+	// Set up iteration over the contents
+	psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
+	psMetadataItem *contentItem = NULL; // Item from the metadata
+
+	if (strcasecmp(extType, "CHIP") == 0) {
+	    // Extensions are chips; Content contains a list of cells
+	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
+		psString extName = contentItem->name; // The name of the extension
+		pmChip *chip = pmChipAlloc(fpa, extName); // The chip
+		chip->extname = extName;// Mark chip to receive FITS data
+		if (contentItem->type != PS_META_STR) {
+		    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+			     contentItem->type);
+		    continue;
+		}
+
+		const char *content = contentItem->data.V; // The content of the extension
+		psTrace(__func__, 7, "Content of %s is: %s\n", extName, content);
+		psList *cellNames = papSplit(content, " ,"); // A list of the component cells
+		psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, NULL);
+		char *cellName = NULL; // The name of a cell
+		while (cellName = psListGetAndIncrement(cellNamesIter)) {
+		    // Get the cell data
+		    psMetadata *cellData = getCellData(camera, cellName);
+		    pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
+		}
+		psFree(cellNamesIter);
+		psFree(cellNames);
+	    }
+
+	} else if (strcasecmp(extType, "CELL") == 0) {
+	    // Extensions are cells; Content contains a chip name and cell type
+	    psMetadata *chips = psMetadataAlloc(); // Given a chip name, holds the chip number
+	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
+		psString extName = contentItem->name; // The name of the extension
+		psTrace(__func__, 1, "Getting %s....\n", extName);
+
+		if (contentItem->type != PS_META_STR) {
+		    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+			     contentItem->type);
+		    continue;
+		}
+		const char *content = contentItem->data.V; // The content of the extension
+		psList *contents = papSplit(content, ": "); // Split the name from the type
+		if (contents->size != 2) {
+		    psLogMsg(__func__, PS_LOG_WARN, "Unable to read contents of %s: ignored.\n", extName);
+		} else {
+		    psString chipName = psListGet(contents, 0); // The name of the chip
+		    psString cellType = psListGet(contents, 1); // The type of cell
+		    psTrace(__func__, 7, "Extension is cell of type %s, from chip %s\n", cellType,
+			    chipName);
+		    
+		    psMetadataItem *chipItem = psMetadataLookup(chips, chipName); // Item containing the chip
+		    pmChip *chip = NULL; // The chip
+		    if (! chipItem) {
+			chip = pmChipAlloc(fpa, chipName);
+			psMetadataAdd(chips, PS_LIST_TAIL, chipName, PS_META_CHIP, "", chip);
+		    } else {
+			chip = psMemIncrRefCounter(chipItem->data.V);
+		    }
+		    // The cell
+		    psArray *images = NULL;
+		    psMetadata *header = NULL;
+		    psMetadata *cellData = getCellData(camera, cellType);
+		    pmCell *cell = pmCellAlloc(chip, cellData, extName); // The cell
+		    cell->extname = extName; // Mark cell to receive FITS data
+
+		    psFree(chip);
+		    psFree(cell);
+		    psFree(chipName);
+		    psFree(cellType);
+		}
+		psFree(contents);
+	    }
+	    psFree(chips);
+
+	} else if (strcasecmp(extType, "NONE") == 0) {
+	    // No extensions; Content contains metadata, each entry is a chip with its component cells
+	    fpa->extname = "PHU";
+	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
+		psString chipName = contentItem->name; // The name of the chip
+
+		if (contentItem->type != PS_META_STR) {
+		    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+			     contentItem->type);
+		    continue;
+		}
+		psString content = contentItem->data.V; // The content of the extension
+		psTrace(__func__, 5, "Component cells are: %s\n", content);
+		pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+		psList *cellNames = papSplit(content, ", "); // Split the list of cells
+		psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false);
+		char *cellName = NULL; // Name of the cell
+		while (cellName = psListGetAndIncrement(cellNamesIter)) {
+		    psTrace(__func__, 7, "Processing cell %s....\n", cellName);
+		    psMetadata *cellData = getCellData(camera, cellName);
+		    pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
+		}
+		psFree(cellNamesIter);
+	    }
+
+	} else {
+	    psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is not CHIP, CELL or NONE.\n");
+	    psFree(fpa);
+	    return NULL;
+	} // Type of extension
+
+	psFree(contentsIter);
+
+    } else if (strcasecmp(phuType, "CHIP") == 0) {
+	// The FITS file contains a single chip only
+	psString chipName = psStringCopy("CHIP"); // Name for chip
+	pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+
+	if (strcasecmp(extType, "NONE") == 0) {
+	    // There are no extensions --- only the PHU
+	    chip->extname = "PHU";
+
+	    const char *contents = psMetadataLookupString(&mdStatus, camera, "CONTENTS");
+	    if (! mdStatus) {
+		psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
+		psFree(fpa);
+		return NULL;
+	    }
+	    psList *cellNames = papSplit(contents, " ,"); // Names of cells
+	    psListIterator *cellIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
+	    psString cellName = NULL;
+	    while (cellName = psListGetAndIncrement(cellIter)) {
+		psMetadata *cellData = getCellData(camera, cellName);
+		pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
+	    }
+	    psFree(cellIter);
+
+	} else if (strcasecmp(extType, "CELL") == 0) {
+	    // Extensions are cells
+	    psMetadata *contents = psMetadataLookupMD(&mdStatus, camera, "CONTENTS"); // The CONTENTS
+	    if (! mdStatus) {
+		psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
+		psFree(fpa);
+		return NULL;
+	    }
+	    
+	    if (strcasecmp(extType, "CELL") != 0) {
+		psLogMsg(__func__, PS_LOG_WARN, "EXTENSIONS in camera definition is %s, but PHU is CHIP.\n"
+			 "EXTENSIONS assumed to be CELL.\n", extType);
+	    }
+
+	    // Iterate through the contents
+	    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
+	    psMetadataItem *contentItem = NULL; // Item from metadata
+	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
+		psString extName = contentItem->name; // The name of the extension
+		// Content is a cell type
+		if (contentItem->type != PS_META_STR) {
+		    psLogMsg(__func__, PS_LOG_WARN,
+			     "CONTENT metadata for extension %s is not of type string, but %x --- ignored\n",
+			     extName, contentItem->type);
+		    continue;
+		}
+		const char *cellType = contentItem->data.V; // The type of cell
+		psTrace(__func__, 5, "Cell type is %s\n", cellType);
+		psArray *images = NULL;
+		psMetadata *header = NULL;
+		psMetadata *cellData = getCellData(camera, cellType);
+		pmCell *cell = pmCellAlloc(chip, cellData, extName); // The cell
+		cell->extname = extName; // Mark cell to receive FITS data
+	    } // Iterating through contents
+	    psFree(contentsIter);
+
+	} else {
+	    psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is neither CELL or NONE.\n");
+	    psFree(fpa);
+	    return NULL;
+	}
+
+    } else {
+	psError(PS_ERR_IO, true,
+		"The PHU type specified in the camera configuration (%s) is not FPA or CHIP.\n",
+		phuType);
+	psFree(fpa);
+	return NULL;
+    }
+
+    return fpa;
+}
+
+// Print out the focal plane structure
+void pmFPAPrint(pmFPA *fpa		// FPA to print
+    )
+{
+    psTrace(__func__, 0, "FPA:\n");
+    if (fpa->extname) {
+	psTrace(__func__, 1, "---> FPA is extension %s.\n", fpa->extname);
+	if (! fpa->pixels) {
+	    psTrace(__func__, 1, "---> NO PIXELS for extension %s\n", fpa->extname);
+	}
+    }
+
+    psArray *chips = fpa->chips;	// Array of chips
+    // Iterate over the FPA
+    for (int i = 0; i < chips->n; i++) {
+	psTrace(__func__, 1, "Chip: %d\n", i);
+	pmChip *chip = chips->data[i]; // The chip
+	if (chip->extname) {
+	    psTrace(__func__, 2, "---> Chip is extension %s.\n", chip->extname);
+	    if (! chip->pixels) {
+		psTrace(__func__, 2, "---> NO PIXELS for extension %s\n", chip->extname);
+	    }
+	}
+	// Iterate over the chip
+	psArray *cells = chip->cells;	// Array of cells
+	for (int j = 0; j < cells->n; j++) {
+	    psTrace(__func__, 2, "Cell: %d\n", j);
+	    pmCell *cell = cells->data[j]; // The cell
+	    if (cell->extname) {
+		psTrace(__func__, 3, "---> Cell is extension %s.\n", cell->extname);
+		if (! cell->pixels) {
+		    psTrace(__func__, 3, "---> NO PIXELS for extension %s\n", cell->extname);
+		}
+	    }
+	    psMetadataPrint(cell->values, 3);
+	    psTrace(__func__, 3, "Readouts:\n");
+	    psArray *readouts = cell->readouts;	// Array of readouts
+	    for (int k = 0; k < readouts->n; k++) {
+		pmReadout *readout = readouts->data[k]; // The readout
+		psImage *image = readout->image; // The image
+		psTrace(__func__, 4, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 + 
+			image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
+			image->numRows);
+		psList *overscans = readout->overscans;	// The list of overscans
+		psListIterator *overscansIter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false);
+		while (image = psListGetAndIncrement(overscansIter)) {
+		    psTrace(__func__, 4, "Overscan: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 + 
+			    image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
+			    image->numRows);
+		}
+		psFree(overscansIter);
+
+	    } // Iterating over cell
+	} // Iterating over chip
+    } // Iterating over FPA
+
+}
Index: /trunk/archive/scripts/src/phase2/pmFPAConstruct.h
===================================================================
--- /trunk/archive/scripts/src/phase2/pmFPAConstruct.h	(revision 4820)
+++ /trunk/archive/scripts/src/phase2/pmFPAConstruct.h	(revision 4820)
@@ -0,0 +1,17 @@
+#ifndef PM_FPA_CONSTRUCT_H
+#define PM_FPA_CONSTRUCT_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Read the contents of a FITS file (format specified by the camera configuration) into memory
+pmFPA *pmFPAConstruct(const psMetadata *camera, // The camera configuration
+		      psDB *db		// Database handle
+    );
+
+// Print out the FPA
+void pmFPAPrint(pmFPA *fpa		// FPA to print
+    );
+
+
+#endif
