Index: trunk/psModules/test/concepts/tap_pmConcepts.c
===================================================================
--- trunk/psModules/test/concepts/tap_pmConcepts.c	(revision 14882)
+++ trunk/psModules/test/concepts/tap_pmConcepts.c	(revision 14882)
@@ -0,0 +1,499 @@
+    /** @file tst_pmConcepts.c
+ *
+ *  @brief Contains the tests for pmConcepts.c:
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-09-18 18:59:30 $
+*   pmConceptSpecAlloc()
+    pmConceptsList()
+*   pmConceptGetRequired()
+*   pmConceptSetRequired()
+    pmConceptRegister()
+    pmConceptsRead()
+*   pmConceptsBlankFPA()
+    pmConceptsReadFPA()
+    pmConceptsWriteFPA()
+*   pmConceptsBlankChip()
+    pmConceptsReadChip()
+    pmConceptsWriteChip()
+*   pmConceptsBlankCell()
+    pmConceptsReadCell()
+    pmConceptsWriteCell()
+*   pmConceptsInit()
+*   pmConceptsDone()
+    pmFPACopyConcepts()
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psMetadataItem *dummyConceptParser(
+    const psMetadataItem *concept,
+    const psMetadataItem *pattern,
+    pmConceptSource source,
+    const psMetadata *cameraFormat,
+    const pmFPA *fpa,
+    const pmChip *chip,
+    const pmCell *cell)
+{
+    if (concept == NULL ||
+        pattern == NULL ||
+        source == PM_CONCEPT_SOURCE_NONE ||
+        cameraFormat == NULL ||
+        fpa == NULL ||
+        chip == NULL ||
+        cell == NULL) {
+        printf("dummyConceptParser() args are NULL\n");
+    }
+    return(NULL);
+}
+
+// FPA.RA and FPA.DEC
+psMetadataItem *dummyConceptFormatter(
+    const psMetadataItem *concept,
+    pmConceptSource source,
+    const psMetadata *cameraFormat,
+    const pmFPA *fpa,
+    const pmChip *chip,
+    const pmCell *cell)
+{
+    if (concept == NULL ||
+        source == PM_CONCEPT_SOURCE_NONE ||
+        cameraFormat == NULL ||
+        fpa == NULL ||
+        chip == NULL ||
+        cell == NULL) {
+        printf("dummyConceptFormatter() args are NULL\n");
+    }
+    return(NULL);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel(".", ERR_TRACE_LEVEL);
+    plan_tests(10);
+    
+    // --------------------------------------------------------------------
+    // Tests for pmConceptSpecAlloc()
+    // Acceptable input parameters.
+    {
+        psMemId id = psMemGetId();
+        psMetadataItem *blank = psMetadataItemAlloc("myItem1", PS_DATA_BOOL, "I am a boolean", true);
+        pmConceptSpec *tmp = pmConceptSpecAlloc(blank, dummyConceptParser,
+            dummyConceptFormatter, true);
+        ok(tmp != NULL, "pmConceptSpecAlloc() returned non-NULL");
+        skip_start(tmp == NULL, 4, "Skipping tests because pmConceptSpecAlloc() returned NULL");
+        ok(tmp->blank == blank, "pmConceptSpecAlloc() set the ->blank member correctly");
+        ok(tmp->parse == dummyConceptParser, "pmConceptSpecAlloc() set the ->parse member correctly");
+        ok(tmp->format == dummyConceptFormatter, "pmConceptSpecAlloc() set the ->format member correctly");
+        ok(tmp->required == true, "pmConceptSpecAlloc() set the ->required member correctly");
+        skip_end();
+        psFree(tmp);
+        psFree(blank);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // NULL input parameters.
+    {
+        psMemId id = psMemGetId();
+        pmConceptSpec *tmp = pmConceptSpecAlloc(NULL, NULL, NULL, false);
+        ok(tmp != NULL, "pmConceptSpecAlloc() returned non-NULL with NULL inputs");
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // Tests for pmConceptGetRequired() and pmConceptSetRequired()
+    // Acceptable input parameters.
+    // We get the "required" for FPA.TELESCOPE, ensure that it is false
+    // then set it to true, then ensure that it is true.
+    {
+        psMemId id = psMemGetId();
+        bool tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA);
+        ok (tmpBool == false, "pmConceptGetRequired() returned true for FPA.TELESCOPE");
+        tmpBool = pmConceptSetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA, true);
+        ok (tmpBool == true, "pmConceptSetRequired() returned true for FPA.TELESCOPE");
+        tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA);
+        ok (tmpBool == true, "pmConceptGetRequired() returned true for FPA.TELESCOPE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Test pmConceptGetRequired() with a few incorrect concept names
+    // and/or levels.
+    {
+        psMemId id = psMemGetId();
+        bool tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_CHIP);
+        ok (tmpBool == false, "pmConceptGetRequired() returned false for FPA.TELESCOPE with wrong level (CHIP)");
+        tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_CELL);
+        ok (tmpBool == false, "pmConceptGetRequired() returned false for FPA.TELESCOPE with wrong level (CELL)");
+        tmpBool = pmConceptGetRequired("FPA.BADCONCEPTNAME", PM_FPA_LEVEL_FPA);
+        ok (tmpBool == false, "pmConceptGetRequired() returned false for FPA.BADCONCEPTNAME");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // Tests for pmConceptsInit(), pmConceptsDone()
+    // We determine if pmConceptsInit() was successful by looking at the
+    // "required" for the CHIP.XPARITY concept at level PM_FPA_LEVEL_CHIP
+    {
+        psMemId id = psMemGetId();
+        pmConceptsDone();
+        // pmConceptsInit() should return TRUE after pmConceptsDone() is called
+        ok(true == pmConceptsInit(), "pmConceptsInit() returned TRUE");
+
+        // FPA concepts
+        ok(false == pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TELESCOPE at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.INSTRUMENT", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.INSTRUMENT at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.DETECTOR", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.DETECTOR at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.CAMERA", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.CAMERA at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.FOCUS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.FOCUS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.AIRMASS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.AIRMASS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.FILTERID", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.FILTERID at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.FILTER", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.FILTER at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.POSANGLE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.POSANGLE at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.RADECSYS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.RADECSYS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.RA", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.RA at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.DEC", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.DEC at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.OBSTYPE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.OBSTYPE at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.OBJECT", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.OBJECT at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.ALT", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.ALT at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.AZ", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.AZ at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.TIMESYS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TIMESYS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.TIME", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TIME at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.TEMP", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TEMP at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.EXPOSURE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.EXPOSURE at level: FPA");
+
+        // Chip concepts
+        ok(true == pmConceptGetRequired("CHIP.XPARITY", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.XPARITY at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.YPARITY", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.YPARITY at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.X0", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.X0 at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.Y0", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.Y0 at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.XSIZE", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.XSIZE at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.YSIZE", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.YSIZE at level: CHIP");
+        ok(false == pmConceptGetRequired("CHIP.TEMP", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.TEMP at level: CHIP");
+
+        // Cell concepts
+        ok(true == pmConceptGetRequired("CELL.GAIN", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.GAIN at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.READNOISE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.READNOISE at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.SATURATION", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.SATURATION at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.BAD", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.BAD at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XPARITY", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XPARITY at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YPARITY", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YPARITY at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.READDIR", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.READDIR at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.EXPOSURE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.EXPOSURE at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.DARKTIME", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.DARKTIME at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.TRIMSEC", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.TRIMSEC at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.BIASSEC", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.BIASSEC at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XBIN", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XBIN at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YBIN", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YBIN at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.TIMESYS", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.TIMESYS at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.TIME", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.TIME at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.X0", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.X0 at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.Y0", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.Y0 at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XSIZE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XSIZE at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YSIZE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YSIZE at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XWINDOW", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XWINDOW at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YWINDOW", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YWINDOW at level: CELL");
+
+        // The 2nd pmConceptsInit() should return FALSE after pmConceptsInit() is called
+        ok(false == pmConceptsInit(), "pmConceptsInit() returned FALSE");
+
+        pmConceptsDone();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // --------------------------------------------------------------------
+    // Tests for pmConceptsBlankFPA()
+    // Verify error with NULL input.
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmConceptsBlankFPA(NULL);
+        ok(rc == false, "pmConceptsBlankFPA() returned FALSE with NULL input");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // -----------------------------------------------------------------------------
+    // Tests for pmConceptsBlankFPA()
+    // Call with valid data.  We test by ensuring the first 5 metadata items were
+    // added to fpa->concepts.
+    {
+        psMemId id = psMemGetId();
+        bool mdok;
+        char *tmpStr;
+        psF32 tmpF32;
+        psF64 tmpF64;
+        psS32 tmpS32;
+        pmFPA *fpa = pmFPAAlloc(NULL);
+        bool rc = false;
+
+        // First junk items to fpa->concepts so that we know they are later blanked.
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.TELESCOPE", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.INSTRUMENT", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.DETECTOR", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.CAMERA", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.FOCUS", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.AIRMASS", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.FILTERID", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.FILTER", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.POSANGLE", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.RADECSYS", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.OBSTYPE", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.OBJECT", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.ALT", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.AZ", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddS32(fpa->concepts, PS_LIST_TAIL, "FPA.TIMESYS", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.TEMP", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.EXPOSURE", PS_META_REPLACE, "", 22.0);
+        ok(rc, "Set dummy data in fpa->concepts");
+
+        rc = pmConceptsBlankFPA(fpa);
+        ok(rc == true, "pmConceptsBlankFPA() returned TRUE with valid input data");
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.TELESCOPE");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.TELESCOPE was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.INSTRUMENT");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.INSTRUMENT was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.DETECTOR");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.DETECTOR was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.CAMERA");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.CAMERA was cleared (%s)", tmpStr);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.FOCUS");
+        ok(mdok && isnan(tmpF32), "FPA.FOCUS was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.AIRMASS");
+        ok(mdok && isnan(tmpF32), "FPA.AIRMASS was cleared (%f)", tmpF32);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.FILTERID");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.FILTERID was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.FILTER");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.FILTER was cleared (%s)", tmpStr);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.POSANGLE");
+        ok(mdok && isnan(tmpF32), "FPA.POSANGLE was cleared (%f)", tmpF32);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.RADECSYS");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.RADECSYS was cleared (%s)", tmpStr);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.RA");
+        ok(mdok && isnan(tmpF64), "FPA.RA was cleared (%f)", tmpF64);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.DEC");
+        ok(mdok && isnan(tmpF64), "FPA.DEC was cleared (%f)", tmpF64);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.OBSTYPE");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.OBSTYPE was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.OBJECT");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.OBJECT was cleared (%s)", tmpStr);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.ALT");
+        ok(mdok && isnan(tmpF64), "FPA.ALT was cleared (%f)", tmpF64);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.AZ");
+        ok(mdok && isnan(tmpF64), "FPA.AZ was cleared (%f)", tmpF64);
+        tmpS32 = psMetadataLookupS32(&mdok, fpa->concepts, "FPA.TIMESYS");
+        ok(mdok && -1 == tmpS32, "FPA.TIMESYS was cleared (%d)", tmpS32);
+        // XXX: Add code to make sure it was cleared.
+        psMetadataItem *tmpMI = psMetadataLookup(fpa->concepts, "FPA.TIME");
+        ok(tmpMI != NULL, "FPA.TIME was cleared");
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.TEMP");
+        ok(mdok && isnan(tmpF32), "FPA.TEMP was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.TEMP");
+        ok(mdok && isnan(tmpF32), "FPA.TEMP was cleared (%f)", tmpF32);
+
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // -----------------------------------------------------------------------------
+    // Tests for pmConceptsBlankChip()
+    // Call with valid data.  We test by ensuring the first 5 metadata items were
+    // added to chip->concepts.
+    {
+        psMemId id = psMemGetId();
+        bool mdok;
+        psF32 tmpF32;
+        psS32 tmpS32;
+        pmChip *chip = pmChipAlloc(NULL, NULL);
+        bool rc = false;
+
+        // First junk items to chip->concepts so that we know they are later blanked.
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.XPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.YPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.X0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.Y0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.XSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.YSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddF32(chip->concepts, PS_LIST_TAIL, "CHIP.TEMP", PS_META_REPLACE, "", 22.0);
+        ok(rc, "Set dummy data in chip->concepts");
+
+        rc = pmConceptsBlankChip(chip);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XPARITY");
+        ok(mdok && 0 == tmpS32, "CHIP.XPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YPARITY");
+        ok(mdok && 0 == tmpS32, "CHIP.YPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0");
+        ok(mdok && 0 == tmpS32, "CHIP.X0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0");
+        ok(mdok && 0 == tmpS32, "CHIP.Y0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XSIZE");
+        ok(mdok && 0 == tmpS32, "CHIP.XSIZE was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YSIZE");
+        ok(mdok && 0 == tmpS32, "CHIP.YSIZE was cleared (%d)", tmpS32);
+        tmpF32 = psMetadataLookupF32(&mdok, chip->concepts, "CHIP.TEMP");
+        ok(mdok && isnan(tmpF32), "CHIP.TEMP was cleared (%d)", tmpF32);
+
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // -----------------------------------------------------------------------------
+    // Tests for pmConceptsBlankCell()
+    // Call with valid data.  We test by ensuring the first 5 metadata items were
+    // added to cell->concepts.
+    {
+        psMemId id = psMemGetId();
+        bool mdok;
+        psF32 tmpF32;
+        psS32 tmpS32;
+        pmCell *cell = pmCellAlloc(NULL, NULL);
+        bool rc = false;
+
+        // First junk items to cell->concepts so that we know they are later blanked.
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.GAIN", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.READNOISE", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.READDIR", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.SATURATION", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.BAD", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.DARKTIME", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.X0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.Y0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XWINDOW", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YWINDOW", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.", PS_META_REPLACE, "", 22);
+
+
+        ok(rc, "Set dummy data in cell->concepts");
+
+        rc = pmConceptsBlankCell(cell);
+        ok(rc == true, "pmConceptsBlankCELL() returned TRUE with valid input data");
+
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.GAIN");
+        ok(mdok && isnan(tmpF32), "CELL.GAIN was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.READNOISE");
+        ok(mdok && isnan(tmpF32), "CELL.READNOISE was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.SATURATION");
+        ok(mdok && isnan(tmpF32), "CELL.SATURATION was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD");
+        ok(mdok && isnan(tmpF32), "CELL.BAD was cleared (%f)", tmpF32);
+
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY");
+        ok(mdok && 0 == tmpS32, "CELL.XPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY");
+        ok(mdok && 0 == tmpS32, "CELL.YPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR");
+        ok(mdok && 0 == tmpS32, "CELL.READDIR was cleared (%d)", tmpS32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.EXPOSURE");
+        ok(mdok && isnan(tmpF32), "CELL.EXPOSURE was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.DARKTIME");
+        ok(mdok && isnan(tmpF32), "CELL.DARKTIME was cleared (%f)", tmpF32);
+        // XXX: Add code to make sure it was cleared.
+        psMetadataItem *tmpMI = psMetadataLookup(cell->concepts, "CELL.TRIMSEC");
+        ok(tmpMI != NULL, "CELL.TRIMSEC was cleared");
+        tmpMI = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
+        ok(tmpMI != NULL, "CELL.BIASSEC was cleared");
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN");
+        ok(mdok && 0 == tmpS32, "CELL.XBIN was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN");
+        ok(mdok && 0 == tmpS32, "CELL.YBIN was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.TIMESYS");
+        ok(mdok && -1 == tmpS32, "CELL.TIMESYS was cleared (%d)", tmpS32);
+        // XXX: Add code to make sure it was cleared.
+        tmpMI = psMetadataLookup(cell->concepts, "CELL.TIME");
+        ok(tmpMI != NULL, "CELL.TIME was cleared");
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0");
+        ok(mdok && 0 == tmpS32, "CELL.X0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0");
+        ok(mdok && 0 == tmpS32, "CELL.Y0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XSIZE");
+        ok(mdok && 0 == tmpS32, "CELL.XSIZE was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YSIZE");
+        ok(mdok && 0 == tmpS32, "CELL.YSIZE was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XWINDOW");
+        ok(mdok && 0 == tmpS32, "CELL.XWINDOW was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YWINDOW");
+        ok(mdok && 0 == tmpS32, "CELL.YWINDOW was cleared (%d)", tmpS32);
+
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+}
Index: trunk/psModules/test/concepts/tap_pmConceptsAverage.c
===================================================================
--- trunk/psModules/test/concepts/tap_pmConceptsAverage.c	(revision 14882)
+++ trunk/psModules/test/concepts/tap_pmConceptsAverage.c	(revision 14882)
@@ -0,0 +1,486 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+// XXX: Use better name for the temporary FITS file
+// XXX: The code to generate and free the FPA hierarchy was copied from
+// tap-pmFPA.c.  EIther include it directly, or library, or something.
+// Also, get rid of the manual free functions and use psFree() once
+// it correctly frees child members
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_FPAS		4
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = generateSimpleReadout(cell);
+    }
+
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+//    chip->hdu = pmHDUAlloc("chipExtName");
+//
+//    bool rc = pmConfigFileRead(&chip->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+//    if (!rc) {
+//        diag("pmConfigFileRead() was unsuccessful (from generateSimpleChip())");
+//    }
+//
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = generateSimpleCell(chip);
+    }
+
+    // XXX: Add code to initialize chip pmConcepts
+
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+//    fpa->hdu = pmHDUAlloc("fpaExtName");
+//
+//    bool rc = pmConfigFileRead(&fpa->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+//    if (!rc) {
+//        diag("pmConfigFileRead() was unsuccessful (from generateSimpleFPA())");
+//    }
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = generateSimpleChip(fpa);
+    }
+
+    // XXX: Eventually, when you finish the pmConcepts tests, add full concept
+    // reading code from wherever.
+    pmConceptsBlankFPA(fpa);
+//    bool mdok;
+//    psMetadata *fileData = psMetadataLookupMetadata(&mdok, fpa->hdu->format, "FILE");
+//    char *fpaNameHdr = psMetadataLookupStr(&mdok, fileData, "FPA.NAME");
+//    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.NAME", PS_META_REPLACE, NULL, fpaNameHdr);
+
+    return(fpa);
+}
+
+// XXX: This should only be necessary until the psFree() functions for
+// FPA/chip/cell/readout correctly free all child chips/cells/readouts
+void myFreeCell(pmCell *cell)
+{
+    for (int k = 0 ; k < cell->readouts->n ; k++) {
+        psFree(cell->readouts->data[k]);
+    }
+    psFree(cell);
+}
+
+void myFreeChip(pmChip *chip) {
+    for (int j = 0 ; j < chip->cells->n ; j++) {
+        myFreeCell(chip->cells->data[j]);
+    }
+    psFree(chip);
+}
+
+void myFreeFPA(pmFPA *fpa)
+{
+    for (int i = 0 ; i < fpa->chips->n ; i++) {
+        myFreeChip(fpa->chips->data[i]);
+    }
+    psFree(fpa);
+}
+
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(50);
+
+    // ----------------------------------------------------------------------
+    // pmConceptsAverageFPAs() tests: NULL input pmFPA *target
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *targetFPA = generateSimpleFPA(camera);
+        pmFPA *sourceFPA[NUM_FPAS];
+
+        sourceFPA[0] = generateSimpleFPA(camera);
+        psList *sources = psListAlloc(sourceFPA[0]);
+        for (int fpaID = 1 ; fpaID < NUM_FPAS ; fpaID++) {
+            sourceFPA[fpaID] = generateSimpleFPA(camera);
+            bool rc = psListAdd(sources, PS_LIST_HEAD, sourceFPA[fpaID]);
+            ok(rc, "Successfully added FPA %d to list", fpaID);
+	}
+        ok(!pmConceptsAverageFPAs(NULL, sources), "pmConceptsAverage(NULL, sources) returned FALSE");
+
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            myFreeFPA(sourceFPA[fpaID]);
+	}
+        psFree(sources);
+        myFreeFPA(targetFPA);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsAverageFPAs() tests: NULL input psList *sources
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *targetFPA = generateSimpleFPA(camera);
+        pmFPA *sourceFPA[NUM_FPAS];
+
+        sourceFPA[0] = generateSimpleFPA(camera);
+        psList *sources = psListAlloc(sourceFPA[0]);
+        for (int fpaID = 1 ; fpaID < NUM_FPAS ; fpaID++) {
+            sourceFPA[fpaID] = generateSimpleFPA(camera);
+            bool rc = psListAdd(sources, PS_LIST_HEAD, sourceFPA[fpaID]);
+            ok(rc, "Successfully added FPA %d to list", fpaID);
+	}
+        ok(!pmConceptsAverageFPAs(targetFPA, NULL), "pmConceptsAverage(NULL, sources) returned FALSE");
+
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            myFreeFPA(sourceFPA[fpaID]);
+	}
+        psFree(sources);
+        myFreeFPA(targetFPA);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsAverageFPAs() tests: acceptable inputs
+    // XXX: There's a memory leak somewhere in this test, not sure where.
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *targetFPA = generateSimpleFPA(camera);
+        pmFPA *sourceFPA[NUM_FPAS];
+
+        // Ensure that the FPA.TIME average is computed correctly
+        psList *sources = NULL;
+        psF64 actualTime = 0.0;
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            sourceFPA[fpaID] = generateSimpleFPA(camera);
+            psTime *fpaTime = psMetadataLookupPtr(NULL, (sourceFPA[fpaID])->concepts, "FPA.TIME");
+            // Add a small value to the psTime so that we can test/ensure that pmConceptsAverageFPAs()
+            // is actually calculating an average.
+            fpaTime->sec += (double) (fpaID * 1000);
+            actualTime+= psTimeToMJD(fpaTime);
+            if (0 == fpaID) {
+                sources = psListAlloc(sourceFPA[fpaID]);
+	    } else {
+                bool rc = psListAdd(sources, PS_LIST_HEAD, sourceFPA[fpaID]);
+                ok(rc, "Successfully added FPA %d to list", fpaID);
+	    }
+	}
+        ok(pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned FALSE");
+        actualTime/= (float) NUM_FPAS;
+        psTime *fpaTime = psMetadataLookupPtr(NULL, targetFPA->concepts, "FPA.TIME");
+        ok(abs(actualTime - psTimeToMJD(fpaTime)) < 1e-4, "pmConceptsAverageFPAs() calculated the average time correctly");
+
+
+        // Replace the FPA.TIMESYS with a non-conforming value, verify that pmConceptsAverageFPAs() returnes an error        
+        psTimeType timeSys = psMetadataLookupS32(NULL, sourceFPA[0]->concepts, "FPA.TIMESYS");
+        psMetadataAddS32(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.TIMESYS", PS_META_REPLACE, NULL, timeSys+10);
+        ok(!pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned FALSE with nonequal FPA.TIMESYS metadata");
+        psMetadataAddS32(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.TIMESYS", PS_META_REPLACE, NULL, timeSys);
+        ok(pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned TRUE with equal FPA.TIMESYS metadata");
+
+
+        // Replace the FPA.TIMESYS with a non-conforming value, verify that pmConceptsAverageFPAs() returnes an error        
+        psString filter = psMetadataLookupStr(NULL, sourceFPA[0]->concepts, "FPA.FILTER");
+        psMetadataAddStr(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.FILTER", PS_META_REPLACE, NULL, "BOGUS STRING");
+        ok(!pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned FALSE with nonequal FPA.FILTER metadata");
+        psMetadataAddStr(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.FILTER", PS_META_REPLACE, NULL, filter);
+        ok(pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned TRUE with equal FPA.FILTER metadata");
+
+        // Free data, check for memory leaks
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            myFreeFPA(sourceFPA[fpaID]);
+	}
+        psFree(sources);
+        myFreeFPA(targetFPA);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmConceptsAverageCells() tests: NULL input pmFPA *target
+    // bool pmConceptsAverageCells(pmCell *target, psList *sources, psRegion *trimsec, psRegion *biassec, bool same)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *tgtCamera = psMetadataAlloc();
+        pmFPA *tgtFPA = generateSimpleFPA(tgtCamera);
+        pmChip *tgtChip = tgtFPA->chips->data[0];
+        pmCell *tgtCell = tgtChip->cells->data[0];
+        psMetadata *srcCamera = psMetadataAlloc();
+        pmFPA *srcFPA = generateSimpleFPA(srcCamera);
+        pmChip *srcChip = srcFPA->chips->data[0];
+
+        psList *sources = NULL;
+        psF32 tstGain = 0.0;
+        psF32 tstReadnoise = 0.0;
+        psF32 tstExposure = 0.0;
+        psF32 tstDarktime = 0.0;
+        psF32 tstSaturation = 0.0;
+        psF32 tstBad = 0.0;
+        
+        for (int cellID = 0 ; cellID < srcChip->cells->n ; cellID++) {
+            pmCell *cell = srcChip->cells->data[cellID];
+            // Set the various concepts which we will test later
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.GAIN", PS_META_REPLACE, NULL, 0.0 + (float) cellID);
+            tstGain+= 0.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.READNOISE", PS_META_REPLACE, NULL, 10.0 + (float) cellID);
+            tstReadnoise+= 10.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.EXPOSURE", PS_META_REPLACE, NULL, 20.0 + (float) cellID);
+            tstExposure+= 20.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.DARKTIME", PS_META_REPLACE, NULL, 30.0 + (float) cellID);
+            tstDarktime+= 30.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.SATURATION", PS_META_REPLACE, NULL, 40.0 + (float) cellID);
+            if (cellID == 0)
+                tstSaturation = 40.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.BAD", PS_META_REPLACE, NULL, 50.0 + (float) cellID);
+            if (cellID == (srcChip->cells->n - 1))
+                tstBad = 50.0 + (float) cellID;
+            if (cellID == 0) {
+                sources = psListAlloc(srcChip->cells->data[cellID]);
+	    } else {
+                bool rc = psListAdd(sources, PS_LIST_HEAD, srcChip->cells->data[cellID]);
+                ok(rc, "Successfully added cell %d to list", cellID);
+	    }
+	}
+        tstGain /= (psF64) srcChip->cells->n;
+        tstReadnoise /= (psF64) srcChip->cells->n;
+        tstExposure /= (psF64) srcChip->cells->n;
+        tstDarktime /= (psF64) srcChip->cells->n;
+
+        psRegion *trimsec = psRegionAlloc(0, 1, 2, 3);
+        psRegion *biassec = psRegionAlloc(4, 5, 6, 7);
+
+        // Ensure pmConceptsAverageCells() returns NULL with NULL tgtCell input
+        bool rc = pmConceptsAverageCells(NULL, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with NULL sources input");
+
+        // Ensure pmConceptsAverageCells() returns NULL with NULL sources input
+        rc = pmConceptsAverageCells(tgtCell, NULL, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with NULL targetCell input");
+
+        // Ensure pmConceptsAverageCells() returns NULL with sources->n = 0
+        sources->n = 0;
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with NULL sources->n = 0");
+        sources->n = NUM_CELLS;
+
+        // Call pmConceptsAverageCells() with acceptable input data
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(rc, "pmConceptsAverageCells() returned TRUE with acceptable input data");
+        psF32 tmpF32;
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.GAIN");
+        ok(abs(tmpF32 - tstGain) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.GAIN correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.READNOISE");
+        ok(abs(tmpF32 - tstReadnoise) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.READNOISE correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.EXPOSURE");
+        ok(abs(tmpF32 - tstExposure) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.EXPOSURE correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.DARKTIME");
+        ok(abs(tmpF32 - tstDarktime) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.DARKTIME correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.SATURATION");
+        ok(abs(tmpF32 - tstSaturation) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.SATURATION correctly (%f %f)", tmpF32, tstSaturation);
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.BAD");
+        ok(abs(tmpF32 - tstBad) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.BAD correctly");
+        psRegion *tstTrimsec = psMetadataLookupPtr(NULL, tgtCell->concepts, "CELL.TRIMSEC");
+        ok(tstTrimsec->x0 == 0.0 && tstTrimsec->x1 == 1.0 && tstTrimsec->y0 == 2.0 && tstTrimsec->y1 == 3.0,
+           "pmConceptsAverageCells() set the CELL>TRIMSEC region correctly");
+        psRegion *tstBiassec = psMetadataLookupPtr(NULL, tgtCell->concepts, "CELL.BIASSEC");
+        ok(tstBiassec->x0 == 4.0 && tstBiassec->x1 == 5.0 && tstBiassec->y0 == 6.0 && tstBiassec->y1 == 7.0,
+           "pmConceptsAverageCells() set the CELL>TRIMSEC region correctly");
+
+        psS32 tmpS32;
+        pmCell *srcCell = srcChip->cells->data[NUM_CELLS - 1];
+
+        // Set the CELL.TIMESYS metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.TIMESYS", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.TIMESYS");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.TIMESYS metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.TIMESYS", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.READDIR metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.READDIR", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.READDIR");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.READDIR metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.READDIR", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.XBIN metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.XBIN", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.XBIN");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.XBIN metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.XBIN", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.YBIN metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.YBIN", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.YBIN");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.YBIN metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.YBIN", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.X0 metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.X0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, true);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.X0 metadata (same = true)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.Y0 metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.Y0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, true);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.Y0 metadata (same = true)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.X0 metadata unequal, verify that no error occurs with same==false
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.X0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.X0 metadata (same = false)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.Y0 metadata unequal, verify that no error occurs with same==false
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.Y0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.Y0 metadata (same = false)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, tmpS32);
+
+        myFreeFPA(tgtFPA);
+        myFreeFPA(srcFPA);
+        psFree(tgtCamera);
+        psFree(srcCamera);
+        psFree(biassec);
+        psFree(trimsec);
+        psFree(sources);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+}
+
+
Index: trunk/psModules/test/concepts/tap_pmConceptsPhotcode.c
===================================================================
--- trunk/psModules/test/concepts/tap_pmConceptsPhotcode.c	(revision 14882)
+++ trunk/psModules/test/concepts/tap_pmConceptsPhotcode.c	(revision 14882)
@@ -0,0 +1,114 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(25);
+
+
+    // ----------------------------------------------------------------------
+    // pmConceptsPhotcodeForView() tests: NULL pmConfig input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(NULL, file, view),
+          "pmConceptsPhotcodeForView(NULL, file, view) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: NULL pmConfig input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(NULL, file, view),
+          "pmConceptsPhotcodeForView(NULL, file, view) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: NULL pmFPAfile input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(config, NULL, view),
+          "pmConceptsPhotcodeForView(config, NULL, view) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: NULL pmFPAview input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(config, file, NULL),
+          "pmConceptsPhotcodeForView(config, file, NULL) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: acceptable inputs
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../config/data/SampleIPPConfig";
+        psS32 argc = 3;
+        pmConfig *config = pmConfigRead(&argc, str, "RecipeName");
+        ok(config, "pmConfigRead() returned non-NULL");
+        pmFPAfile *file = pmFPAfileAlloc();
+        // XXX: Insert code to read a pmFPAfile correctly
+        pmFPAview *view = pmFPAviewAlloc(0);
+
+        bool rc;
+        psMetadata *recipe  = psMetadataLookupPtr(&rc, config->recipes, "PPIMAGE");
+        char *rule = psMetadataLookupStr(&rc, recipe, "PHOTCODE.RULE");
+        psString goodPhotcode = pmFPAfileNameFromRule(rule, file, view);
+
+        psString testPhotcode = pmConceptsPhotcodeForView(config, file, view);
+        ok(testPhotcode, "pmConceptsPhotcodeForView(config, file, view) returned non-NULL");
+        ok(!strcmp(goodPhotcode, testPhotcode), "pmConceptsPhotcodeForView() produced the correct string");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
+//HERE
Index: trunk/psModules/test/concepts/tap_pmConceptsUpdate.c
===================================================================
--- trunk/psModules/test/concepts/tap_pmConceptsUpdate.c	(revision 14882)
+++ trunk/psModules/test/concepts/tap_pmConceptsUpdate.c	(revision 14882)
@@ -0,0 +1,290 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+// XXX: Use better name for the temporary FITS file
+// XXX: The code to generate and free the FPA hierarchy was copied from
+// tap-pmFPA.c.  EIther include it directly, or library, or something.
+// Also, get rid of the manual free functions and use psFree() once
+// it correctly frees child members
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = generateSimpleReadout(cell);
+    }
+
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+//    chip->hdu = pmHDUAlloc("chipExtName");
+//
+//    bool rc = pmConfigFileRead(&chip->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+//    if (!rc) {
+//        diag("pmConfigFileRead() was unsuccessful (from generateSimpleChip())");
+//    }
+//
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = generateSimpleCell(chip);
+    }
+
+    // XXX: Add code to initialize chip pmConcepts
+
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+//    fpa->hdu = pmHDUAlloc("fpaExtName");
+//
+//    bool rc = pmConfigFileRead(&fpa->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+//    if (!rc) {
+//        diag("pmConfigFileRead() was unsuccessful (from generateSimpleFPA())");
+//    }
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = generateSimpleChip(fpa);
+    }
+
+    // XXX: Eventually, when you finish the pmConcepts tests, add full concept
+    // reading code from wherever.
+    pmConceptsBlankFPA(fpa);
+//    bool mdok;
+//    psMetadata *fileData = psMetadataLookupMetadata(&mdok, fpa->hdu->format, "FILE");
+//    char *fpaNameHdr = psMetadataLookupStr(&mdok, fileData, "FPA.NAME");
+//    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.NAME", PS_META_REPLACE, NULL, fpaNameHdr);
+
+    return(fpa);
+}
+
+// XXX: This should only be necessary until the psFree() functions for
+// FPA/chip/cell/readout correctly free all child chips/cells/readouts
+void myFreeCell(pmCell *cell)
+{
+    for (int k = 0 ; k < cell->readouts->n ; k++) {
+        psFree(cell->readouts->data[k]);
+    }
+    psFree(cell);
+}
+
+void myFreeChip(pmChip *chip) {
+    for (int j = 0 ; j < chip->cells->n ; j++) {
+        myFreeCell(chip->cells->data[j]);
+    }
+    psFree(chip);
+}
+
+void myFreeFPA(pmFPA *fpa)
+{
+    for (int i = 0 ; i < fpa->chips->n ; i++) {
+        myFreeChip(fpa->chips->data[i]);
+    }
+    psFree(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(7);
+
+
+    // ----------------------------------------------------------------------
+    // pmConceptsUpdate() tests: NULL inputs
+    // bool pmConceptsUpdate(const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+    {
+        psMemId id = psMemGetId();
+        ok(pmConceptsUpdate(NULL, NULL, NULL), "pmConceptsUpdate(NULL, NULL, NULL) returned TRUE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+    // pmConceptsUpdate() tests: acceptable inputs
+    // XXX: Must add tests for the BIASSEC concepts
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        bool rc = pmConceptsUpdate(NULL, NULL, cell);
+
+        // Calculate expected myTrimsec
+        bool xStatus, yStatus; // Status of MD lookups
+        psImageBinning *binning = psImageBinningAlloc();
+        binning->nXbin = psMetadataLookupS32(&xStatus, cell->concepts, "CELL.XBIN");
+        binning->nYbin = psMetadataLookupS32(&yStatus, cell->concepts, "CELL.YBIN");
+        psRegion *goodTrimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+        double goodTrimsecX0 = goodTrimsec->x0;
+        double goodTrimsecX1 = goodTrimsec->x1;
+        double goodTrimsecY0 = goodTrimsec->y0;
+        double goodTrimsecY1 = goodTrimsec->y1;
+        goodTrimsecX0/= binning->nXbin;
+        goodTrimsecX1/= binning->nXbin;
+        goodTrimsecY0/= binning->nYbin;
+        goodTrimsecY1/= binning->nYbin;
+        goodTrimsecX0 = (int) goodTrimsec->x0;
+        if (goodTrimsec->x1 > (int)goodTrimsec->x1) {
+            goodTrimsecX1 = (int)goodTrimsec->x1 + 1;
+         } else {
+            goodTrimsecX1 = (int)goodTrimsec->x1;
+	 }
+        goodTrimsecY0 = (int)goodTrimsec->y0;
+        if (goodTrimsec->y1 > (int)goodTrimsec->y1) {
+            goodTrimsecY1 = (int)goodTrimsec->y1 + 1;
+	} else {
+            goodTrimsecY1 = (int)goodTrimsec->y1;
+	}
+        psFree(binning);
+
+        // Add CELL.TRIMSEC.UPDATE concept to cell->concepts
+        psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.TRIMSEC.UPDATE", 0, NULL, 32);
+        psS32 num = psMetadataLookupS32(&rc, cell->concepts, "CELL.TRIMSEC.UPDATE");
+        ok(rc, "Successfully added CELL.TRIMSEC.UPDATE to cell->concepts (%d)", num);
+
+        // Add CELL.BIASSEC.UPDATE concept to cell->concepts
+        psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.BIASSEC.UPDATE", 0, NULL, 32);
+        num = psMetadataLookupS32(&rc, cell->concepts, "CELL.BIASSEC.UPDATE");
+        ok(rc, "Successfully added CELL.BIASSEC.UPDATE to cell->concepts (%d)", num);
+
+        // Call pmConceptsUpdate to update the CELL.TRIMSEC concept
+        ok(rc, "pmConceptsUpdate(NULL, NULL, pmCell) returned TRUE");
+
+        psRegion *testTrimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        ok(testTrimsec->x1 == goodTrimsecX1 && testTrimsec->y1 == goodTrimsecY1 &&
+           testTrimsec->x0 == goodTrimsecX0 && testTrimsec->y0 == goodTrimsecY0,
+           "pmConceptsUpdate() updated CELL.TRIMSEC correctly (%f %f)",
+            testTrimsec->x1, testTrimsec->y1);
+
+        // XXX: This fails but the problem might be with the test code
+        if (0) {
+            num = psMetadataLookupS32(&rc, cell->concepts, "CELL.TRIMSEC.UPDATE");
+            ok(!rc, "pmConceptsUpdate() removed CELL.TRIMSEC.UPDATE to cell->concepts (%d)", num);
+        }
+
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
+
