Index: /trunk/archive/scripts/src/Makefile
===================================================================
--- /trunk/archive/scripts/src/Makefile	(revision 4092)
+++ /trunk/archive/scripts/src/Makefile	(revision 4092)
@@ -0,0 +1,27 @@
+SHELL = /bin/sh
+CC = gcc
+CFLAGS += -O2 -g -std=c99 -I/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/include/ -D_GNU_SOURCE
+PSLIB += -L/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/lib/ -lpslib -lgsl -lgslcblas -lfftw3f -lsla -lcfitsio -lm -lxml2 -lmysqlclient
+LDFLAGS += $(PSLIB)
+
+OBJS = test_pmFPAfromHeader.o pmCameraFromHeader.o pmFPAFromHeader.o psAdditionals.o papStuff.o
+
+TARGET = test_pmFPAfromHeader
+
+.PHONY: tags clean empty
+
+.c.o:
+		$(CC) -c $(CFLAGS) $(OPTFLAGS) $<
+
+$(TARGET):	$(OBJS)
+		$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(OPTFLAGS)
+
+clean:
+		-$(RM) *.o gmon.* profile.txt
+
+empty:		clean
+		-$(RM) $(TARGET) TAGS
+
+# Tags for emacs
+tags:
+		etags `find . -name \*.[ch] -print`
Index: /trunk/archive/scripts/src/concepts.txt
===================================================================
--- /trunk/archive/scripts/src/concepts.txt	(revision 4092)
+++ /trunk/archive/scripts/src/concepts.txt	(revision 4092)
@@ -0,0 +1,60 @@
+We need to be able to do two things:
+
+1. Given an identifier (root name, observation number, whatever), read
+   in the whole FPA (e.g., for phase 1 and 3).  This means that we
+   need to be able to identify the files for each chip, and the
+   extensions.
+
+2. Given a file, identify what it is: observation number + chip/cell.
+   This is so that we can identify the filter, airmass, etc.  We can't
+   expect that these will always be specified in the chip level FITS
+   headers, but might be stored in a database under the observation
+   number.
+
+That is, we need to be able to identify the components given the
+whole, and we need to identify the whole given the component.
+
+
+
+Here is the list of IPP concepts, each of which must be specified somewhere.
+
+FPA-level:
+FPA.AIRMASS		Airmass at which the observation is made (boresight)
+FPA.FILTER		Filter used
+FPA.POSANGLE		Position angle
+FPA.RA			Right Ascension of boresight
+FPA.DEC			Declination of boresight
+FPA.RADECSYS		System of RA,Dec (e.g., J2000 or ICRS)
+FPA.NAME		An identifier (e.g., observation number) for the FPA instance
+
+Chip-level:
+CHIP.NAME		The name of the chip (unique within the FPA) --- set at read
+CHIP.FPA		FPA identifier
+
+Cell/Readout-level:
+CELL.NAME		The name of the cell (unique within the parent chip) --- set at read
+CELL.CHIP		Chip identifier --- might be set at read
+CELL.DATE		Date of observation start
+CELL.TIME		Time of observation start
+CELL.EXPOSURE		Exposure time of image
+CELL.DARKTIME		Dark time for image
+CELL.BIASSEC		Overscan region(s)
+CELL.TRIMSEC		Trim region
+CELL.GAIN		CCD gain (e/ADU)
+CELL.READNOISE		CCD read noise (e)
+
+There is no reason why concepts higher up can't be specified lower
+down (multiple times, at the risk of introducing the potential for
+value to become out of sync).  And there is no reason why (most)
+concepts lower down can't be specified higher up, and simply be
+inherited.  The exception to this is the OBSNUM (observation number),
+which allows an FPA to be identified given a cell, etc.
+
+
+These are optional:
+
+DB.HOST		Database host name (for psDBInit)
+DB.NAME		Database name (for psDBInit)
+DB.USER		Database user name (for psDBInit)
+DB.PASSWORD	Database password (for psDBInit)
+
Index: /trunk/archive/scripts/src/gpc1_raw.config
===================================================================
--- /trunk/archive/scripts/src/gpc1_raw.config	(revision 4092)
+++ /trunk/archive/scripts/src/gpc1_raw.config	(revision 4092)
@@ -0,0 +1,71 @@
+# The raw GPC data comes off the telescope with each of the chips stored in separate files
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	PS1
+	DETECTOR	STR	GPC1
+	EXTEND		BOOL	T
+END
+
+# How to read this data
+PHU		STR	CHIP	# The FITS file represents a single chip
+EXTENSIONS	STR	CELL	# The extensions represent cells
+EXTENSION_KEY	STR	EXTNAME	# You get the extensions by looking at the EXTNAME header
+IDENTIFIER	STR	OBSID	# We identify the observation by the observation Id in the header
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, type
+	cell00	STR	pitch10u
+	cell01	STR	pitch10u
+	cell10	STR	pitch10u
+	cell11	STR	pitch12u
+END
+
+# Specify the cell data
+CELLS	METADATA
+	pitch10u	METADATA
+		CELL.BIASSEC	STR	VALUE:[1:10,1:614];[625:674,1:614]
+		CELL.TRIMSEC	STR	VALUE:[11:624,1:614]
+	#	CELL.BIASSEC	STR	HEADER:BIASSEC
+	#	CELL.TRIMSEC	STR	HEADER:TRIMSEC
+	END
+
+	pitch12u	METADATA
+		CELL.BIASSEC	STR	VALUE:[1:10,1:512];[523:574,1:512]
+		CELL.TRIMSEC	STR	VALUE:[11:522,1:512]
+	#	CELL.BIASSEC	STR	HEADER:BIASSEC
+	#	CELL.TRIMSEC	STR	HEADER:TRIMSEC
+	END
+END
+
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	POSANG
+	FPA.RA		STR	OBJ-RA
+	FPA.DEC		STR	OBJ-DEC
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.DATE	STR	DATE-OBS
+	CELL.TIME	STR	TIME-OBS
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.RADECSYS	STR	ICRS
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP,CELL
+	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP,CELL
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+
+END
Index: /trunk/archive/scripts/src/ipprc.config
===================================================================
--- /trunk/archive/scripts/src/ipprc.config	(revision 4092)
+++ /trunk/archive/scripts/src/ipprc.config	(revision 4092)
@@ -0,0 +1,16 @@
+### Example .ipprc file
+
+
+### Database configuration
+DBSERVER	STR	ippdb.ifa.hawaii.edu
+DBUSER		STR	ipp
+DBPASSWORD	STR	password
+
+### Setups for each camera system
+INSTRUMENTS	METADATA
+	MEGACAM_RAW	STR	megacam_raw.config
+	MEGACAM_SPLICE	STR	megacam_splice.config
+	GPC1_RAW	STR	gpc1_raw.config
+	PS1_COMCAM	STR	ps1_comcam.config
+END
+
Index: /trunk/archive/scripts/src/megacam_raw.config
===================================================================
--- /trunk/archive/scripts/src/megacam_raw.config	(revision 4092)
+++ /trunk/archive/scripts/src/megacam_raw.config	(revision 4092)
@@ -0,0 +1,73 @@
+# The raw MegaCam data comes off the telescope with each of the chips stored in extensions of a MEF file.
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	MegaCam
+	EXTEND		BOOL	T
+	NEXTEND		S32	72
+END
+
+# How to read this data
+PHU		STR	FPA	# The FITS file represents an entire FPA
+EXTENSIONS	STR	CELL	# The extensions represent cells
+EXTENSION_KEY	STR	EXTNAME	# You get the extensions by looking at the EXTNAME header
+IDENTIFIER	STR	OBSID	# We identify the observation by the observation Id in the header
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:type
+	amp00	STR	ccd00:science
+	amp01	STR	ccd00:science
+	amp02	STR	ccd01:science
+	amp03	STR	ccd01:science
+	guide	STR	guide:guide		# A guide CCD thrown in, just for fun
+END
+
+# Specify the cell data
+CELLS	METADATA
+	science	METADATA	# A science CCD
+		CELL.BIASSEC	STR	VALUE:[1:10,1:4096];[1035:1084,1:4096]
+		CELL.TRIMSEC	STR	VALUE:[11:1034,1:4096]
+	#	CELL.BIASSEC	STR	HEADER:BIASSEC
+	#	CELL.TRIMSEC	STR	HEADER:TRIMSEC
+	END
+
+	guide	METADATA	# A guide CCD
+		CELL.BIASSEC	STR	VALUE:[1:10,1:1024];[1035:1084,1:1024]
+		CELL.TRIMSEC	STR	VALUE:[11:1034,1:1024]
+	#	CELL.BIASSEC	STR	HEADER:BIASSEC
+	#	CELL.TRIMSEC	STR	HEADER:TRIMSEC
+	END
+		
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	POSANG
+	FPA.RA		STR	OBJ-RA
+	FPA.DEC		STR	OBJ-DEC
+	CELL.EXPOSURE	STR	EXPOSURE
+	CELL.DARKTIME	STR	EXPOSURE	# No specific darktime header; use exposure time
+	CELL.DATE	STR	DATE-OBS
+	CELL.TIME	STR	TIME-OBS
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.RADECSYS	STR	ICRS
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+
+END
Index: /trunk/archive/scripts/src/megacam_splice.config
===================================================================
--- /trunk/archive/scripts/src/megacam_splice.config	(revision 4092)
+++ /trunk/archive/scripts/src/megacam_splice.config	(revision 4092)
@@ -0,0 +1,63 @@
+# The processed MecaCam data is stored in single files for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT
+	DETECTOR	STR	MegaCam
+	EXTEND		BOOL	F
+END
+
+# How to read this data
+PHU		STR	FPA	# The FITS file represents an entire FPA
+EXTENSIONS	STR	CHIP	# The extensions represent chips
+EXTENSION_KEY	STR	EXTNAME	# You get the extensions by looking at the EXTNAME header
+IDENTIFIER	STR	OBSID	# We identify the observation by the observation Id in the header
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, components
+	ccd00		STR	LeftAmp RightAmp
+	ccd01		STR	LeftAmp RightAmp
+END
+
+# Specify the cells
+CELLS		METADATA
+	LeftAmp		METADATA
+		CELL.BIASSEC	STR	VALUE:[1:10,1:2048];[2069:2118,1:2048]
+		CELL.TRIMSEC	STR	VALUE:[21:1044,1:2048]
+	#	CELL.BIASSEC	STR	HEADER:BIASSECA
+	#	CELL.TRIMSEC	STR	HEADER:TRIMSECA
+	END
+
+	RightAmp	METADATA
+		CELL.BIASSEC	STR	VALUE:[11:20,1:2048];[2119:2168,1:2048]
+		CELL.TRIMSEC	STR	VALUE:[1045:2068,1:2048]
+	#	CELL.BIASSEC	STR	HEADER:BIASSECB
+	#	CELL.TRIMSEC	STR	HEADER:TRIMSECB
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	POSANG
+	FPA.RA		STR	OBJ-RA
+	FPA.DEC		STR	OBJ-DEC
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.DATE	STR	DATE-OBS
+	CELL.TIME	STR	TIME-OBS
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+
+END		
Index: /trunk/archive/scripts/src/papFocalPlane.c
===================================================================
--- /trunk/archive/scripts/src/papFocalPlane.c	(revision 4092)
+++ /trunk/archive/scripts/src/papFocalPlane.c	(revision 4092)
@@ -0,0 +1,309 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "papFocalPlane.h"
+#include "pmRead.h"
+
+// I'm sure this code, especially the DB lookup, leaks memory something chronic....
+
+
+// Look for a value already cached
+static psMetadataItem *getValueFromCache(const psFPA *fpa, // The FPA that contains the chip
+					 const psChip *chip, // The chip that contains the cell
+					 const psCell *cell, // The cell
+					 const char *valueName // Name of value
+    )
+{
+    // Search the cell
+    psMetadataItem *item = psMetadataLookup(cell->values, valueName); // Item from the lookup
+    if (item) {
+	return item;
+    }
+    // Search the chip
+    item = psMetadataLookup(chip->values, valueName);
+    if (item) {
+	return item;
+    }
+    // Search the FPA
+    item = psMetadataLookup(fpa->values, valueName);
+    return item;			// item is either NULL or points to what was desired
+}
+
+
+// Put a value that we've found into the cache
+static void setValueInCache(psFPA *fpa, // The FPA that contains the chip
+			    psChip *chip, // The chip that contains the cell
+			    psCell *cell, // The cell
+			    const char *valueName, // Name of value
+			    const psMetadataItem *value	// Item containing the value
+    )
+{
+    // We need to create a new psMetadataItem.  I don't think we can't simply hack the existing one, since
+    // that could conceivably cause memory leaks
+    psMetadataItem *newItem = psMetadataItemAlloc(valueName, value->type, value->comment, value->data.V);
+
+    if (fpa) {
+	psMetadataAddItem(fpa->values, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+    }
+    if (chip) {
+	psMetadataAddItem(chip->values, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+    }
+    if (cell) {
+	psMetadataAddItem(cell->values, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+    }
+
+    psFree(newItem);
+}
+
+
+static psMetadataItem *getValueFromHeader(const psFPA *fpa, // The FPA that contains the chip
+					  const psChip *chip, // The chip that contains the cell
+					  const psCell *cell, // The cell
+					  const char *valueName // Name of value
+    )
+{
+    // Look for how to translate the concept into a FITS header name
+    const char *header = psMetadataLookupString(fpa->fits, valueName);
+    if (strlen(header) > 0) {
+	// We have a FITS header to look up --- search each level
+	if (cell->header) {
+	    psMetadataItem *cellItem = psMetadataLookup(cell->header, header);
+	    if (cellItem) {
+		// XXX: Need to clean up before returning
+		setValueInCache(NULL, NULL, cell, cellItem);
+		return cellItem;
+	    }
+	}
+
+	if (chip->header) {
+	    psMetadataItem *chipItem = psMetadataLookup(chip->header, header);
+	    if (chipItem) {
+		// XXX: Need to clean up before returning
+		setValueInCache(NULL, chip, cell, chipItem);
+		return chipItem;
+	    }
+	}
+
+	if (fpa->header) {
+	    psMetadataItem *fpaItem = psMetadataLookup(fpa->header, header);
+	    if (fpaItem) {
+		// XXX: Need to clean up before returning
+		setValueInCache(fpa, chip, cell, fpaItem);
+		return fpaItem;
+	    }
+	}
+    }
+
+    // No header value
+    return NULL;
+}
+
+
+// Look for a default
+static psMetadataItem *getValueFromDefault(const psFPA *fpa, // The FPA that contains the chip
+					   const psChip *chip, // The chip that contains the cell
+					   const psCell *cell, // The cell
+					   const char *valueName // Name of value
+    )
+{
+    psMetadataItem *defItem = psMetadataLookup(fpa->defaults, valueName);
+    if (defItem) {
+	setValueInCache(fpa, chip, cell, defItem);
+    }
+    return defItem;			// defItem is either NULL or points to what was desired
+}
+
+
+// Look for a database lookup
+static psMetadataItem *getValueFromDB(const psFPA *fpa, // The FPA that contains the chip
+				      const psChip *chip, // The chip that contains the cell
+				      const psCell *cell, // The cell
+				      const char *valueName // Name of value
+    )
+{
+    if (fpa->db) {
+	// The database has been initialised
+
+	psMetadata *dbLookup = psMetadataLookupMD(fpa->database, valueName);
+	if (dbLookup) {
+	    const char *tableName = psMetadataLookupString(dbLookup, "TABLE"); // Name of the table
+	    const char *colName = psMetadataLookupString(dbLookup, "COLUMN"); // Name of the column
+	    const char *givenCols = psMetadataLookupString(dbLookup, "GIVENDBCOL");	// Name of "where" columns
+	    const char *givenPS = psMetadataLookupString(dbLookup, "GIVENPS"); // Values for "where" columns
+	    
+	    // Now, need to get the "given"s
+	    if (strlen(givenCols) || strlen(givenPS)) {
+		psList *cols = papSplit(givenCols, ",;"); // List of column names
+		psList *values = papSplit(givenPS, ",;"); // List of value names for the columns
+		psMetadata *selection = psMetadataAlloc(NULL); // The stuff to select in the DB
+		if (cols->size != values->size) {
+		    psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
+			     "the same number of entries --- ignored.\n", valueName);
+		} else {
+		    // Iterators for the lists
+		    psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
+		    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+		    char *column = NULL;	// Name of the column
+		    while (column = psListGetAndIncrement(colsIter)) {
+			const char *name = psListGetAndIncrement(valuesIter); // Name for the value
+			if (!strlen(column) || !strlen(name)) {
+			    psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
+				     " empty --- ignored.\n", valueName);
+			} else {
+			    // Search for the value name
+			    psMetadataItem *item = getValueFromCache(fpa, chip, cell, name);
+			    if (! item) {
+				item = getValueFromHeader(fpa, chip, cell, name);
+			    }
+			    if (! item) {
+				item = getValueFromDefault(fpa, chip, cell, name);
+			    }
+			    if (! item) {
+				psLogMsg(__func__, PS_LOG_ERR, "Unable to find the value name %s for DB "
+					 " lookup on %s --- ignored.\n", name, valueName);
+			    } else {
+				// We need to create a new psMetadataItem.  I don't think we can't simply hack
+				// the existing one, since that could conceivably cause memory leaks
+				psMetadataItem *newItem = psMetadataItemAlloc(valueName, item->type,
+									      item->comment, item->data.V);
+				psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+				psFree(newItem);
+			    }
+			}
+		    } // Iterating through the columns
+		    
+		    psArray *dbResult = psDBSelectRows(fpa->db, tableName, selection, 2); // Result of lookup
+		    // Note that we use limit=2 in order to test if there are multiple rows returned
+
+		    psMetadataItem *result = NULL; // The final result of the DB lookup
+		    if (dbResult->n == 0) {
+			psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n", 
+				 valueName);
+		    } else {
+			if (dbResult-> n > 1) {
+			    psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+				     " using the first one only.\n", valueName);
+			}
+			result = (psMetadataItem*)dbResult->data[0];
+		    }
+		    // XXX: Need to clean up before returning
+		    return result;
+		}
+	    }
+	} // Doing the "given"s.
+    }
+
+    // XXX: Need to clean up before returning
+
+    // If we've fallen through somehow, we didn't get anything.
+    return NULL;
+}
+
+psMetadataItem *psCellGetValue(const psFPA *fpa, // The FPA that contains the cell
+			       const char *chipName, // The name of the chip
+			       const char *cellName, // The name of the cell
+			       const char *valueName // Name of value
+			       )
+{
+    psChip *chip = psMetadataLookupChip(fpa, chipName);	// The chip
+    psCell *cell = psMetadataLookupCell(chip, cellName); // The cell
+
+    // Try cache, headers, database, defaults in order
+    psMetadataItem *item = getValueFromCache(fpa, chip, cell, valueName);
+    if (! item) {
+	item = getValueFromHeader(fpa, chip, cell, valueName);
+    }
+    if (! item) {
+	item = getValueFromDB(fpa, chip, cell, valueName);
+    }
+    if (! item) {
+	item = getValueFromDefault(fpa, chip, cell, valueName);
+    }
+
+    return item;			// item is either NULL or points to what was desired
+}
+
+float psCellGetValueF32(const psFPA *fpa, // The FPA that contains the cell
+			const char *chipName, // The name of the chip
+			const char *cellName, // The name of the cell
+			const char *valueName // Name of value
+			)
+{
+    float value = NAN;			// Result
+    psMetadataItem *item = psCellGetValue(fpa, chipName, cellName, valueName); // The item
+    if (! item) {
+	psError(PS_ERR_IO, true, "Unable to find value %s in %s:%s.\n", valueName, chipName, cellName);
+    } else if (item->type != PS_META_F32) {
+	psError(PS_ERR_IO, true, "Metadata item type for %s (%s:%s) is not F32.\n", valueName, chipName,
+		cellName);
+    } else {
+	value = item->data.F32;
+    }
+
+    psFree(item);
+    return value;
+}
+
+int psCellGetValueS32(const psFPA *fpa, // The FPA that contains the cell
+		      const char *chipName, // The name of the chip
+		      const char *cellName, // The name of the cell
+		      const char *valueName // Name of value
+    )
+{
+    int value = NAN;			// Result
+    psMetadataItem *item = psCellGetValue(fpa, chipName, cellName, valueName); // The item
+    if (! item) {
+	psError(PS_ERR_IO, true, "Unable to find value %s in %s:%s.\n", valueName, chipName, cellName);
+    } else if (item->type != PS_META_S32) {
+	psError(PS_ERR_IO, true, "Metadata item type for %s (%s:%s) is not S32.\n", valueName, chipName,
+		cellName);
+    } else {
+	value = item->data.S32;
+    }
+
+    psFree(item);
+    return value;
+}
+
+
+double psCellGetValueF64(const psFPA *fpa, // The FPA that contains the cell
+			 const char *chipName, // The name of the chip
+			 const char *cellName, // The name of the cell
+			 const char *valueName // Name of value
+			 )
+{
+    double value = NAN;			// Result
+    psMetadataItem *item = psCellGetValue(fpa, chipName, cellName, valueName); // The item
+    if (! item) {
+	psError(PS_ERR_IO, true, "Unable to find value %s in %s:%s.\n", valueName, chipName, cellName);
+    } else if (item->type != PS_META_F64) {
+	psError(PS_ERR_IO, true, "Metadata item type for %s (%s:%s) is not F64.\n", valueName, chipName,
+		cellName);
+    } else {
+	value = item->data.F64;
+    }
+
+    psFree(item);
+    return value;
+}
+
+
+psString psCellGetValueString(const psFPA *fpa, // The FPA that contains the cell
+			      const char *chipName, // The name of the chip
+			      const char *cellName, // The name of the cell
+			      const char *valueName // Name of value
+    )
+{
+    psString value = NULL;		// Result
+    psMetadataItem *item = psCellGetValue(fpa, chipName, cellName, valueName); // The item
+    if (! item) {
+	psError(PS_ERR_IO, true, "Unable to find value %s in %s:%s.\n", valueName, chipName, cellName);
+    } else if (item->type != PS_META_STR) {
+	psError(PS_ERR_IO, true, "Metadata item type for %s (%s:%s) is not STR.\n", valueName, chipName,
+		cellName);
+    } else {
+	value = item->data.V;
+    }
+
+    psFree(item);
+    return value;
+}
Index: /trunk/archive/scripts/src/papFocalPlane.h
===================================================================
--- /trunk/archive/scripts/src/papFocalPlane.h	(revision 4092)
+++ /trunk/archive/scripts/src/papFocalPlane.h	(revision 4092)
@@ -0,0 +1,88 @@
+#ifndef PAP_FOCAL_PLANE_H
+#define PAP_FOCAL_PLANE_H
+
+#include "pslib.h"
+
+typedef struct {
+    // How to get information
+    psMetadata *defaults;		// IPP concepts supplied with defaults
+    psMetadata *database;		// IPP concepts supplied from database lookup
+    psMetadata *fits;			// IPP concepts supplied from FITS headers
+
+    // A source of information
+    psMetadata *header;			// The FITS header
+    psDB *db;				// The database handle
+
+    // Lower levels
+    psMetadata *chips;			// The chips (referred to by name)
+
+    psImage *image;			// The pixel data, if it corresponds to this level
+} psFPA;
+
+typedef struct {
+    // Sources of information
+    psMetadata *values;			// Important values (cached)
+    psMetadata *header;			// The FITS header
+
+    // Lower levels
+    psMetadata *cells;			// The cells (referred to by name)
+
+    psImage *image;			// The pixel data, if it corresponds to this level
+} psChip;
+
+typedef struct {
+    // Sources of information
+    psMetadata *values;			// Important values (cached)
+    psMetadata *header;			// The FITS header
+
+    // Lower levels
+    psArray *readouts;			// The readouts (referred to by number)
+
+    psImage *image;			// The pixel data, if it corresponds to this level
+} psCell;
+
+
+typedef struct {
+    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
+    psImage* image;			// The pixels
+    psList *overscans;			// Array of subimages containing the overscan region(s)
+    psMetadata* values;			// readout-level metadata
+} psReadout;
+
+
+// Look for a particular value for a given cell (referred to by FPA+chip+cell)
+psMetadataItem *psCellGetValue(const psFPA *fpa, // The FPA that contains the cell
+			       const char *chipName, // The name of the chip
+			       const char *cellName, // The name of the cell
+			       const char *valueName // Name of value
+			       );
+
+// Type-specific functions provided as a convenience to the user
+float psCellGetValueF32(const psFPA *fpa, // The FPA that contains the cell
+			const char *chipName, // The name of the chip
+			const char *cellName, // The name of the cell
+			const char *valueName // Name of value
+			);
+int psCellGetValueS32(const psFPA *fpa, // The FPA that contains the cell
+		      const char *chipName, // The name of the chip
+		      const char *cellName, // The name of the cell
+		      const char *valueName // Name of value
+		      );
+double psCellGetValueF64(const psFPA *fpa, // The FPA that contains the cell
+			 const char *chipName, // The name of the chip
+			 const char *cellName, // The name of the cell
+			 const char *valueName // Name of value
+			 );
+psString psCellGetValueString(const psFPA *fpa, // The FPA that contains the cell
+			      const char *chipName, // The name of the chip
+			      const char *cellName, // The name of the cell
+			      const char *valueName // Name of value
+			      );
+
+
+#endif
Index: /trunk/archive/scripts/src/papStuff.c
===================================================================
--- /trunk/archive/scripts/src/papStuff.c	(revision 4092)
+++ /trunk/archive/scripts/src/papStuff.c	(revision 4092)
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <string.h>
+#include "pslib.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
+		char *word = psStringNCopy(&string[start], i - start);
+		(void)psListAdd(values, PS_LIST_TAIL, word);
+		start = i + 1;
+	    }
+	}
+    }
+    if (start < length) {
+	// Copy the last word
+	char *word = psStringNCopy(&string[start], length - start);
+	(void)psListAdd(values, PS_LIST_TAIL, word);
+    }
+
+    return values;
+}
+
Index: /trunk/archive/scripts/src/papStuff.h
===================================================================
--- /trunk/archive/scripts/src/papStuff.h	(revision 4092)
+++ /trunk/archive/scripts/src/papStuff.h	(revision 4092)
@@ -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/pmCameraFromHeader.c
===================================================================
--- /trunk/archive/scripts/src/pmCameraFromHeader.c	(revision 4092)
+++ /trunk/archive/scripts/src/pmCameraFromHeader.c	(revision 4092)
@@ -0,0 +1,103 @@
+#include <stdio.h>
+#include <string.h>
+#include "pslib.h"
+#include "psmodule.h"
+
+// Work out what camera we have, based on the FITS header and a set of rules specified in the IPP
+// configuration
+char *pmCameraFromHeader(const psMetadata *header, // The FITS header
+			 const psMetadata *ipprc // The IPP configuration
+    )
+{
+    bool mdStatus = false;		// Metadata lookup status
+    psMetadata *instruments = psMetadataLookupMD(&mdStatus, ipprc, "INSTRUMENTS");
+    if (mdStatus == false) {
+	psError(PS_ERR_IO, false, "Unable to find INSTRUMENTS in the configuration.\n");
+	return NULL;
+    }
+
+    char *winner = NULL;		// The instrument whose rule first matches the supplied header
+
+    // Iterate over the instruments
+    psMetadataIterator *iterator = psMetadataIteratorAlloc(instruments, PS_LIST_HEAD, NULL); // MD Iterator
+    psMetadataItem *instrumentItem = NULL; // Item from the metadata
+    while (instrumentItem = psMetadataGetAndIncrement(iterator)) {
+	// Open the instrument information
+	psTrace(__func__, 3, "Inspecting instrument %s (%s)\n", instrumentItem->name,
+		instrumentItem->comment);
+	psMetadata *instrument = NULL;	// The instrument metadata
+	if (instrumentItem->type == PS_META_META) {
+	    instrument = instrumentItem->data.md;
+	} else if (instrumentItem->type == PS_META_STR) {
+	    psTrace(__func__, 5, "Reading instrument configuration for %s...\n", instrumentItem->name);
+	    instrument = psMetadataParseConfig(NULL, NULL, instrumentItem->data.V, true);
+	}
+
+	if (! instrument) {
+	    psLogMsg(__func__, PS_LOG_WARN, "Unable to interpret instrument configuration for %s (%s)\n",
+		     instrumentItem->name, instrumentItem->comment);
+	} else {
+	    // Read the rule for that instrument
+	    psMetadata *rule = psMetadataLookupMD(&mdStatus, instrument, "RULE");
+	    if (! mdStatus) {
+		psLogMsg(__func__, PS_LOG_WARN, "Unable to read rule for instrument %s (%s)\n",
+			 instrumentItem->name, instrumentItem->comment);
+	    } else {
+		// Apply the rules
+		psMetadataIterator *ruleIter = psMetadataIteratorAlloc(rule, PS_LIST_HEAD, NULL); // Rule
+											          // iterator
+		bool match = true;	// The header matches the rule until we determine otherwise
+		psMetadataItem *ruleItem = NULL; // Item from the metadata
+		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;
+		    } else {
+			// Check to see if the rule works
+			switch (ruleItem->type) {
+			  case PS_META_STR:
+			    match = (strcmp(ruleItem->data.V, headerItem->data.V) == 0) ? true : false;
+			    break;
+			  case PS_TYPE_S32:
+			    if (ruleItem->data.S32 != headerItem->data.S32) {
+				match = false;
+			    }
+			    break;
+			  case PS_TYPE_F32:
+			    if (ruleItem->data.F32 != headerItem->data.F32) {
+				match = false;
+			    }
+			    break;
+			  case PS_TYPE_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 for an instrument
+
+		if (match) {
+		    if (! winner) {
+			// This is the first match
+			winner = instrumentItem->name;
+		    } else {
+			// We have a duplicate match
+			psLogMsg(__func__, PS_LOG_WARN,
+				 "Additional instrument found that matches the rules: %s\n",
+				 instrumentItem->name);
+		    }
+		}
+	    }
+
+	} // Done inspecting the instrument
+		    
+    } // Done looking at all instruments
+    psFree(iterator);
+
+    return winner;
+}
Index: /trunk/archive/scripts/src/pmCameraFromHeader.h
===================================================================
--- /trunk/archive/scripts/src/pmCameraFromHeader.h	(revision 4092)
+++ /trunk/archive/scripts/src/pmCameraFromHeader.h	(revision 4092)
@@ -0,0 +1,10 @@
+#ifndef PM_CAMERA_FROM_HEADER_H
+#define PM_CAMERA_FROM_HEADER_H
+
+// Work out what camera we have, based on the FITS header and a set of rules specified in the IPP
+// configuration
+char *pmCameraFromHeader(const psMetadata *header, // The FITS header
+			 const psMetadata *ipprc // The IPP configuration
+    );
+
+#endif
Index: /trunk/archive/scripts/src/pmFPAFromHeader.c
===================================================================
--- /trunk/archive/scripts/src/pmFPAFromHeader.c	(revision 4092)
+++ /trunk/archive/scripts/src/pmFPAFromHeader.c	(revision 4092)
@@ -0,0 +1,280 @@
+#include <stdio.h>
+#include <string.h>
+#include "pslib.h"
+#include "psmodule.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Copy a metadata
+static psMetadata *metadataCopy(psMetadata *md)
+{
+    psMetadata *new = psMetadataAlloc();// The metadata to return
+    psMetadataIterator *mdIter = psMetadataIteratorAlloc(md, PS_LIST_HEAD, NULL); // Iterator for the MD
+    psMetadataItem *item = NULL;	// Item from the metadata
+    while (item = psMetadataGetAndIncrement(mdIter)) {
+	if (! psMetadataAddItem(new, item, PS_LIST_TAIL, PS_META_REPLACE)) {
+	    psLogMsg(__func__, PS_LOG_WARN, "Unable to add item (%s: %s) to metadata: ignored\n", item->name,
+		     item->comment);
+	}
+    }
+    psFree(mdIter);
+
+    return new;
+}
+
+// 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 NULL;
+    } else {
+	psMetadata *newCell = metadataCopy(cellData); // A copy of the cell, to be returned
+	return newCell;
+    }
+}
+	
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+psMetadata *pmFPAfromHeader(const psMetadata *camera // The camera configuration
+    )
+{
+ 
+#if 0
+    psMetadata *headers = psFitsReadHeaderSet(fitsFile); // The headers of every extension
+    if (! headers) {
+	psError(PS_ERR_BAD_DATA, false, "Unable to read FITS headers.\n");
+	return NULL;
+    }
+#endif
+
+    // Generate a build tree
+    psMetadata *fpa = psMetadataAlloc(); // 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 extensions?
+    if (strcmp(phuType, "FPA") == 0) {
+	// The FITS file contains an entire FPA
+
+	psMetadata *contents = psMetadataLookupMD(&mdStatus, camera, "CONTENTS"); // The CONTENTS
+	if (! mdStatus) {
+	    printf("Get here.\n");
+	    psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
+	    psFree(fpa);
+	    return NULL;
+	}
+
+	bool extIsChips;		// Are the extensions chips (true) or cells (false)?
+	if (strcmp(extType, "CHIP") == 0) {
+	    extIsChips = true;	// Extensions are chips
+	} else if (strcmp(extType, "CELL") == 0) {
+	    extIsChips = false;	// Extensions are cells
+	} else {
+	    psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is neither CHIP or CELL.\n");
+	    psFree(fpa);
+	    return NULL;
+	}
+
+	// Iterate through the contents
+	psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
+	psMetadataItem *contentItem = NULL; // Item from the metadata
+	while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
+	    const char *extName = contentItem->name; // The name of the extension
+		
+	    if (extIsChips) {
+		// Content contains a list of cells
+		if (contentItem->type != PS_META_STR) {
+		    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+			     contentItem->type);
+		} else {
+		    const char *content = contentItem->data.V; // The content of the extension
+		    psMetadata *chip = psMetadataAlloc(); // The chip
+		    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 *cell = getCellData(camera, cellName);
+			psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", extName);
+			// Add the cell to the chip
+			psMetadataAdd(chip, PS_LIST_TAIL, cellName, PS_META_META, "", cell);
+		    }
+		    psFree(cellNamesIter);
+		    psFree(cellNames);
+		    // Add the chip onto the FPA
+		    psMetadataAdd(fpa, PS_LIST_TAIL, extName, PS_META_META, "", chip);
+		}
+	    } else {
+		// Content contains a chip name and cell type
+		if (contentItem->type != PS_META_STR) {
+		    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
+			     contentItem->type);
+		} else {
+		    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 {
+			// The name of the chip
+			const char *chipName = psListGet(contents, 0);
+			// The type of cell
+			const char *cellType = psListGet(contents, 1);
+
+			psMetadata *chip = psMetadataLookupMD(&mdStatus, fpa, chipName); // The chip
+			if (! mdStatus && ! chip) {
+			    chip = psMetadataAlloc();
+			    psMetadataAdd(fpa, PS_LIST_TAIL, chipName, PS_META_META, "", chip);
+			}
+			// The cell
+			psMetadata *cell = getCellData(camera, cellType);
+			psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", extName);
+			// Add the cell to the chip
+			psMetadataAdd(chip, PS_LIST_TAIL, extName, PS_META_META, "", cell);
+		    }
+		}
+
+	    } // Type of extension
+	} // Iterating through contents
+	psFree(contentsIter);
+
+    } else if (strcmp(phuType, "CHIP") == 0) {
+	// The FITS file contains a single chip only
+
+	psMetadata *chip = psMetadataAlloc(); // The chip
+	psMetadataAdd(fpa, PS_LIST_TAIL, "CHIP", PS_META_META, "", chip);
+	
+	if (strcmp(extType, "NONE") == 0) {
+	    // There are no extensions --- only the 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
+	    const char *cellName = NULL;
+	    while (cellName = psListGetAndIncrement(cellIter)) {
+		psMetadata *cell = getCellData(camera, cellName);
+		psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", "PHU");
+		// Add the cell to the chip
+		psMetadataAdd(chip, PS_LIST_TAIL, cellName, PS_META_META, "", cell);
+	    }
+	    psFree(cellIter);
+	} else if (strcmp(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 (strcmp(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)) {
+		const char *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);
+		} else {
+		    const char *cellType = contentItem->data.V; // The type of cell
+		    
+		    // The cell
+		    psMetadata *cell = getCellData(camera, cellType);
+		    psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", extName);
+		    // Add the cell to the chip
+		    psMetadataAdd(chip, PS_LIST_TAIL, extName, PS_META_META, "", cell);
+		}
+	    } // 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;
+    }
+
+    
+    // Print out the build tree
+    psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator for FPA
+    psMetadataItem *fpaItem = NULL;	// Item from metadata
+    psTrace("pmFPAfromHeader", 0, "FPA:\n");
+    while (fpaItem = psMetadataGetAndIncrement(fpaIter)) {
+	if (fpaItem->type != PS_META_META) {
+	    psError(PS_ERR_IO, false, "FPA metadata does not contain chip metadata.\n");
+	} else {
+	    psTrace("pmFPAfromHeader", 1, "Chip: %s\n", fpaItem->name);
+	    psMetadata *chip = fpaItem->data.V;	// The chip
+	    psMetadataIterator *chipIter = psMetadataIteratorAlloc(chip, PS_LIST_HEAD, NULL); // Iterator for
+											      // chip
+	    psMetadataItem *chipItem = NULL; // Item from metadata
+	    while (chipItem = psMetadataGetAndIncrement(chipIter)) {
+		if (chipItem->type != PS_META_META) {
+		    psError(PS_ERR_IO, false, "Chip metadata does not contain cell metadata.\n");
+		} else {
+		    psTrace("pmFPAfromHeader", 2, "Cell: %s\n", chipItem->name);
+		    psMetadata *cell = chipItem->data.V; // The cell
+		    psMetadataIterator *cellIter = psMetadataIteratorAlloc(cell, PS_LIST_HEAD, NULL); // Iterator
+												  // for cell
+		    psMetadataItem *cellItem = NULL; // Item from metadata
+		    while (cellItem = psMetadataGetAndIncrement(cellIter)) {
+			switch(cellItem->type) {
+			  case PS_META_STR:
+			    psTrace("pmFPAfromHeader", 3, "%s: %s\n", cellItem->name, cellItem->data.V);
+			    break;
+			  case PS_META_F32:
+			    psTrace("pmFPAfromHeader", 3, "%s: %f\n", cellItem->name, cellItem->data.F32);
+			    break;
+			  case PS_META_META:
+			    psTrace("pmFPAfromHeader", 3, "%s:\n", cellItem->name);
+			    psMetadataPrint(cellItem->data.V, 4);
+			    break;
+			  default:
+			    psError(PS_ERR_IO, false, "Unknown type for cell (%x).\n", cellItem->type);
+			}
+		    } // Iterating through cell
+		    psFree(cellIter);
+		}
+	    } // Iterating through chip
+	    psFree(chipIter);
+	}
+    } // Iterating through FPA
+    psFree(fpaIter);
+
+    return fpa;
+
+}
Index: /trunk/archive/scripts/src/pmFPAFromHeader.h
===================================================================
--- /trunk/archive/scripts/src/pmFPAFromHeader.h	(revision 4092)
+++ /trunk/archive/scripts/src/pmFPAFromHeader.h	(revision 4092)
@@ -0,0 +1,8 @@
+#ifndef PM_FPA_FROM_HEADER_H
+#define PM_FPA_FROM_HEADER_H
+
+// Read in an FPA
+psMetadata *pmFPAfromHeader(const psMetadata *camera // The camera configuration
+    );
+
+#endif
Index: /trunk/archive/scripts/src/test_pmFPAfromHeader.c
===================================================================
--- /trunk/archive/scripts/src/test_pmFPAfromHeader.c	(revision 4092)
+++ /trunk/archive/scripts/src/test_pmFPAfromHeader.c	(revision 4092)
@@ -0,0 +1,31 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "psmodule.h"
+
+int main(int argc, char *argv[])
+{
+    if (argc != 2) {
+	printf("Usage: %s CONFIG\n", argv[0]);
+	exit(EXIT_FAILURE);
+    }
+    const char *configName = argv[1];
+	
+    int badLines = 0;			// Number of bad lines in camera configuration
+
+    (void)psTraceSetLevel("pmFPAfromHeader", 10);
+
+    psMetadata *camera = psMetadataParseConfig(NULL, &badLines, configName, true); // Camera config
+
+    if (badLines > 0) {
+	printf("%d bad lines encountered while reading %s\n", badLines, configName);
+    }
+
+    //psMetadataPrint(camera, 0);
+   
+    psMetadata *fpa = pmFPAfromHeader(camera);
+
+    psFree(camera);
+    psFree(fpa);
+
+    return 0;
+}
