Index: /branches/eam_branch_20070817/psModules/src/imcombine/pmStackReject.c
===================================================================
--- /branches/eam_branch_20070817/psModules/src/imcombine/pmStackReject.c	(revision 14637)
+++ /branches/eam_branch_20070817/psModules/src/imcombine/pmStackReject.c	(revision 14637)
@@ -0,0 +1,123 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmSubtraction.h"
+#include "pmSubtractionKernels.h"
+
+#define PIXEL_LIST_BUFFER 100           // Number of pixels to add to list at a time
+
+psPixels *pmStackReject(const psPixels *in, float threshold, const psArray *regions,
+                        const psArray *solutions, const pmSubtractionKernels *kernels)
+{
+    PS_ASSERT_PIXELS_NON_NULL(in, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(threshold, 0.0, NULL);
+    PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(threshold, 1.0, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(regions, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(solutions, NULL);
+    PS_ASSERT_ARRAYS_SIZE_EQUAL(regions, solutions, NULL);
+    PS_ASSERT_PTR_NON_NULL(kernels, NULL);
+
+    // Get the original image size
+    int numRegions = regions->n;        // Number of regions
+    int numCols = 0, numRows = 0;       // Size of original image
+    int minCols = INT_MAX, minRows = INT_MAX; // Minimum coordinate for image --- should be 0,0
+    for (int i = 0; i < numRegions; i++) {
+        psRegion *region = regions->data[i]; // Region of interest
+        if (region->x0 < minCols) {
+            minCols = region->x0;
+        }
+        if (region->y0 < minRows) {
+            minRows = region->y0;
+        }
+        if (region->x1 > numCols) {
+            numCols = region->x1;
+        }
+        if (region->y1 > numRows) {
+            numRows = region->y1;
+        }
+    }
+    if (minCols != 0 || minRows != 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Some error with image regions --- minimum coordinate is not 0,0");
+        return NULL;
+    }
+
+    psImage *mask = psPixelsToMask(NULL, in, psRegionSet(0, numCols, 0, numRows), 0x01); // Mask image
+    psImage *image = psImageCopy(NULL, mask, PS_TYPE_F32); // Floating-point version, so we can convolve
+    psFree(mask);
+
+    // Convolve the image with the kernel --- we're basically applying a matched filter and then thresholding
+    psImage *convolved = NULL;          // Convolved image
+    for (int i = 0; i < numRegions; i++) {
+        psRegion *region = regions->data[i]; // Region of interest
+        psVector *solution = solutions->data[i]; // Solution of interest
+        if (!pmSubtractionConvolve(&convolved, NULL, NULL, image, NULL, NULL, 0, region, solution, kernels)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to convolve mask image in region %d.", i);
+            psFree(convolved);
+            psFree(image);
+            return NULL;
+        }
+
+        // Need to adjust the thresholding level for the normalisation of the kernel --- the application of
+        // the kernel may scale the unit level that we've inserted.
+
+        // Image of the kernel at the centre of the region
+        float xNorm = (region->x0 + 0.5 * (region->x1 - region->x0) - numCols/2.0) / (float)numCols;
+        float yNorm = (region->y0 + 0.5 * (region->y1 - region->y0) - numRows/2.0) / (float)numRows;
+        psImage *kernel = pmSubtractionKernelImage(solution, kernels, xNorm, yNorm);
+        if (!kernel) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+            psFree(convolved);
+            psFree(image);
+            return NULL;
+        }
+        float sum = 0.0;
+        for (int y = 0; y < kernel->numRows; y++) {
+            for (int x = 0; x < kernel->numCols; x++) {
+                sum += kernel->data.F32[y][x];
+            }
+        }
+        psFree(kernel);
+
+        psImage *subConv = psImageSubset(convolved, *region); // Sub-image of convolved image
+        psBinaryOp(subConv, subConv, "*", psScalarAlloc(1.0 / sum, PS_TYPE_F32));
+    }
+    psFree(image);
+
+    // Threshold the convolved image
+    psPixels *bad = psPixelsAllocEmpty(PIXEL_LIST_BUFFER); // List of pixels that should be masked
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            if (convolved->data.F32[y][x] > threshold) {
+                bad = psPixelsAdd(bad, PIXEL_LIST_BUFFER, x, y);
+            }
+        }
+    }
+    psFree(convolved);
+
+    // Now, we want to convolve the original pixels properly
+    mask = psPixelsToMask(NULL, bad, psRegionSet(0, numCols, 0, numRows), 0xff);
+    int size = kernels->size;           // Size of kernels
+    for (int i = 0; i < bad->n; i++) {
+        int xPix = bad->data[i].x, yPix = bad->data[i].y; // Coordinates of interest
+        // Convolution limits
+        int xMin = PS_MAX(xPix - size, 0);
+        int xMax = PS_MIN(xPix + size, numCols - 1);
+        int yMin = PS_MAX(yPix - size, 0);
+        int yMax = PS_MIN(yPix + size, numRows - 1);
+        for (int y = yMin; y <= yMax; y++) {
+            for (int x = xMin; x <= xMax; x++) {
+                mask->data.PS_TYPE_MASK_DATA[y][x] = 0xff;
+            }
+        }
+    }
+
+    bad = psPixelsFromMask(bad, mask, 0xff);
+    psFree(mask);
+
+    return bad;
+}
Index: /branches/eam_branch_20070817/psModules/src/imcombine/pmStackReject.h
===================================================================
--- /branches/eam_branch_20070817/psModules/src/imcombine/pmStackReject.h	(revision 14637)
+++ /branches/eam_branch_20070817/psModules/src/imcombine/pmStackReject.h	(revision 14637)
@@ -0,0 +1,19 @@
+#ifndef PM_STACK_REJECT_H
+#define PM_STACK_REJECT_H
+
+#include <pslib.h>
+#include <pmSubtractionKernels.h>
+
+/// Given a list of pixels from the convolved image, find the corresponding (smaller subset of) pixels in the
+/// original image, and then convolve those to get the list of all pixels which should be rejected
+///
+/// We apply a matched filter to the corresponding mask image, and threshold to find the original pixels
+psPixels *pmStackReject(const psPixels *in, ///< List of pixels in the convolved image
+                        float threshold, ///< Threshold on convolved image, 0..1
+                        const psArray *regions, ///< Array of image regions for image
+                        const psArray *solutions, ///< Array of solution vectors for image
+                        const pmSubtractionKernels *kernels ///< Kernel parameters
+    );
+
+
+#endif
Index: /branches/eam_branch_20070817/psModules/src/imcombine/pmSubtractionMatch.c
===================================================================
--- /branches/eam_branch_20070817/psModules/src/imcombine/pmSubtractionMatch.c	(revision 14637)
+++ /branches/eam_branch_20070817/psModules/src/imcombine/pmSubtractionMatch.c	(revision 14637)
@@ -0,0 +1,398 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmSubtractionKernels.h"
+#include "pmSubtractionStamps.h"
+#include "pmSubtraction.h"
+#include "pmSubtractionMatch.h"
+
+
+//#define TESTING
+
+// Output memory usage information
+static void memCheck(const char *where)
+{
+#ifdef TESTING
+    psMemBlock **leaks = NULL;
+    int numLeaks = psMemCheckLeaks(0, &leaks, NULL, true);
+    size_t largestSize = 0;
+    psMemId largest = 0;
+    size_t totalSize = 0;
+    for (int i = 0; i < numLeaks; i++) {
+        psMemBlock *mb = leaks[i];
+        totalSize += mb->userMemorySize;
+        if (mb->userMemorySize > largestSize) {
+            largestSize = mb->userMemorySize;
+            largest = mb->id;
+        }
+    }
+    psFree(leaks);
+    fprintf(stderr, "%s:\n", where);
+    fprintf(stderr, "    Memory in use: %zd\n", totalSize);
+    fprintf(stderr, "    Largest block: %ld\n", largest);
+    fprintf(stderr, "    sbrk(): %zd\n", (size_t)sbrk(0));
+#endif
+    return;
+}
+
+
+bool pmSubtractionMatch(pmReadout *convolved, const pmReadout *reference, const pmReadout *input,
+                        int footprint, float regionSize, float stampSpacing, float threshold,
+                        const char *stampsName, float targetWidth, pmSubtractionKernelsType type,
+                        int size, int order, const psVector *isisWidths, const psVector *isisOrders,
+                        int inner, int ringsOrder, int binning, int iter, float rej, psMaskType maskBad,
+                        psMaskType maskBlank)
+{
+    PS_ASSERT_PTR_NON_NULL(convolved, false);
+    PS_ASSERT_PTR_NON_NULL(reference, false);
+    PS_ASSERT_IMAGE_NON_NULL(reference->image, false);
+    PS_ASSERT_IMAGE_TYPE(reference->image, PS_TYPE_F32, false);
+    if (reference->mask) {
+        PS_ASSERT_IMAGE_NON_NULL(reference->mask, false);
+        PS_ASSERT_IMAGE_TYPE(reference->mask, PS_TYPE_MASK, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(reference->mask, reference->image, false);
+    }
+    if (reference->weight) {
+        PS_ASSERT_IMAGE_NON_NULL(reference->weight, false);
+        PS_ASSERT_IMAGE_TYPE(reference->weight, PS_TYPE_F32, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(reference->weight, reference->image, false);
+    }
+    if (input) {
+        PS_ASSERT_IMAGE_NON_NULL(input->image, false);
+        PS_ASSERT_IMAGE_TYPE(input->image, PS_TYPE_F32, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(input->image, reference->image, false);
+        if (input->mask) {
+            PS_ASSERT_IMAGE_NON_NULL(input->mask, false);
+            PS_ASSERT_IMAGE_TYPE(input->mask, PS_TYPE_MASK, false);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(input->mask, reference->image, false);
+        }
+        if (input->weight) {
+            PS_ASSERT_IMAGE_NON_NULL(input->weight, false);
+            PS_ASSERT_IMAGE_TYPE(input->weight, PS_TYPE_F32, false);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(input->weight, reference->image, false);
+        }
+    }
+    PS_ASSERT_INT_NONNEGATIVE(footprint, false);
+    // regionSize can be just about anything (except maybe negative, but it can be NAN)
+    PS_ASSERT_FLOAT_LARGER_THAN(stampSpacing, 0.0, false);
+    // Don't care what threshold is
+    // stampsName may be anything
+    // targetWidth can be just about anything (except maybe negative, but it can be NAN)
+    // We'll check kernel type when we allocate the kernels
+    PS_ASSERT_INT_POSITIVE(size, false);
+    PS_ASSERT_INT_NONNEGATIVE(order, false);
+    if (isisWidths || isisOrders) {
+        PS_ASSERT_VECTOR_NON_NULL(isisWidths, false);
+        PS_ASSERT_VECTOR_TYPE(isisWidths, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_NON_NULL(isisOrders, false);
+        PS_ASSERT_VECTOR_TYPE(isisOrders, PS_TYPE_S32, false);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(isisWidths, isisOrders, false);
+    }
+    PS_ASSERT_INT_NONNEGATIVE(inner, false);
+    PS_ASSERT_INT_NONNEGATIVE(ringsOrder, false);
+    PS_ASSERT_INT_POSITIVE(binning, false);
+    PS_ASSERT_INT_POSITIVE(iter, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+    // Don't care about maskBad
+    // Don't care about maskBlank
+
+    // Reset the output readout, just in case
+    if (convolved->image) {
+        psFree(convolved->image);
+        convolved->image = NULL;
+    }
+    if (convolved->mask) {
+        psFree(convolved->mask);
+        convolved->mask = NULL;
+    }
+    if (convolved->weight) {
+        psFree(convolved->weight);
+        convolved->weight = NULL;
+    }
+
+    psImage *inImage = NULL, *inMask = NULL; // Input image, mask, weight
+    if (input) {
+        inImage = input->image;
+        inMask = input->mask;
+    }
+
+    // Where does our weight map come from?
+    psImage *weight = NULL;             // Weight image to use
+    if (input && input->weight) {
+        weight = input->weight;
+    } else if (reference->weight) {
+        weight = reference->weight;
+    } else if (input) {
+        weight = input->image;
+    } else {
+        weight = reference->image;
+    }
+
+    int numCols = reference->image->numCols, numRows = reference->image->numRows; // Image dimensions
+
+    memCheck("start");
+
+    // Mask for subtraction
+    psImage *subMask = pmSubtractionMask(reference->mask, inMask, maskBad, size, footprint);
+
+    memCheck("mask");
+
+    // Kernel basis functions
+    pmSubtractionKernels *kernels = pmSubtractionKernelsGenerate(type, size, order, isisWidths, isisOrders,
+                                                                 inner, binning, ringsOrder);
+    psMetadataAddPtr(convolved->analysis, PS_LIST_TAIL, "SUBTRACTION.KERNEL", PS_DATA_UNKNOWN,
+                     "Subtraction kernels", kernels);
+    psArray *stamps = NULL;             // Stamps for matching PSF
+    psVector *solution = NULL;          // Solution to match PSF
+
+
+    memCheck("kernels");
+
+    int xRegions = 1, yRegions = 1;     // Number of iso-kernel regions
+    float xRegionSize = 0, yRegionSize = 0; // Size of iso-kernel regions
+    psRegion *region = NULL;            // Iso-kernel region
+    psString regionString = NULL;       // String for region
+    if (isfinite(regionSize) && regionSize != 0.0) {
+        xRegions = numCols / regionSize + 1;
+        yRegions = numRows / regionSize + 1;
+        xRegionSize = (float)numCols / (float)xRegions;
+        yRegionSize = (float)numRows / (float)yRegions;
+        region = psRegionAlloc(NAN, NAN, NAN, NAN);
+    }
+
+    // Iterate over iso-kernel regions
+    for (int j = 0; j < yRegions; j++) {
+        for (int i = 0; i < xRegions; i++) {
+            psTrace("psModules.imcombine", 1, "Subtracting region %d of %d...\n",
+                    j * xRegions + i + 1, xRegions * yRegions);
+            if (region) {
+                *region = psRegionSet((int)(i * xRegionSize), (int)((i + 1) * xRegionSize),
+                                      (int)(j * yRegionSize), (int)((j + 1) * yRegionSize));
+                psFree(regionString);
+                regionString = psRegionToString(*region);
+                psTrace("psModules.imcombine", 3, "Iso-kernel region: %s out of %d,%d\n",
+                        regionString, numCols, numRows);
+            }
+
+            int numRejected = -1;               // Number of rejected stamps in each iteration
+            for (int k = 0; k < iter && numRejected != 0; k++) {
+                psTrace("psModules.imcombine", 2, "Iteration %d...\n", k);
+
+                if (stampsName && strlen(stampsName) > 0) {
+                    psTrace("psModules.imcombine", 3, "Reading stamps from %s...\n", stampsName);
+                    iter = 1;           // There is no iterating because we use all the stamps we have
+                    psVector *xStamp = NULL, *yStamp = NULL, *fluxStamp = NULL; // Stamp positions and fluxes
+                    if (input) {
+                        // We have x, y because the target is provided by the input image
+                        psArray *stampData = psVectorsReadFromFile(stampsName, "%f %f"); // Stamp positions
+                        xStamp = stampData->data[0];
+                        yStamp = stampData->data[1];
+                    } else {
+                        // We have x, y and flux in order to generate a target
+                        psArray *stampData = psVectorsReadFromFile(stampsName, "%f %f %f"); // Stamp positions
+                        xStamp = stampData->data[0];
+                        yStamp = stampData->data[1];
+                        fluxStamp = stampData->data[2];
+                    }
+
+                    // Correct for IRAF/FITS (unit-offset) positions to C (zero-offset) positions
+                    psBinaryOp(xStamp, xStamp, "-", psScalarAlloc(1.0, PS_TYPE_F32));
+                    psBinaryOp(yStamp, yStamp, "-", psScalarAlloc(1.0, PS_TYPE_F32));
+
+                    stamps = pmSubtractionSetStamps(xStamp, yStamp, fluxStamp, subMask, region);
+                } else {
+                    psTrace("psModules.imcombine", 3, "Finding stamps...\n");
+                    stamps = pmSubtractionFindStamps(stamps, reference->image, subMask, region,
+                                                     threshold, stampSpacing);
+                }
+                if (!stamps) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to find stamps.");
+                    goto ERROR;
+                }
+
+                memCheck("  find stamps");
+
+                if (!input && !pmSubtractionGenerateStamps(stamps, targetWidth, footprint, kernels)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to generate target stamps.");
+                    goto ERROR;
+                }
+
+                psTrace("psModules.imcombine", 3, "Extracting stamps...\n");
+                if (!pmSubtractionExtractStamps(stamps, reference->image, inImage, weight,
+                                                footprint, kernels)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to extract stamps.");
+                    goto ERROR;
+                }
+
+                memCheck("   extract stamps");
+
+                psTrace("psModules.imcombine", 3, "Calculating equation...\n");
+                if (!pmSubtractionCalculateEquation(stamps, kernels, footprint)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto ERROR;
+                }
+
+                memCheck("  calculate equation");
+
+                psTrace("psModules.imcombine", 3, "Solving equation...\n");
+                solution = pmSubtractionSolveEquation(solution, stamps);
+                if (!solution) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto ERROR;
+                }
+
+                memCheck("  solve equation");
+
+                psTrace("psModules.imcombine", 3, "Rejecting stamps...\n");
+                numRejected = pmSubtractionRejectStamps(stamps, subMask, solution, footprint, rej, kernels);
+                if (numRejected < 0) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to reject stamps.");
+                    goto ERROR;
+                }
+                psLogMsg("psModules.imcombine", PS_LOG_INFO, "%d stamps rejected on iteration %d.",
+                         numRejected, k);
+
+                memCheck("  reject stamps");
+            }
+
+            if (numRejected > 0) {
+                psTrace("psModules.imcombine", 3, "Solving equation...\n");
+                solution = pmSubtractionSolveEquation(solution, stamps);
+                if (!solution) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto ERROR;
+                }
+            }
+            psFree(stamps);
+            stamps = NULL;
+
+            memCheck("solution");
+
+            {
+                psTrace("psModules.imcombine", 2, "Generating diagnostics...\n");
+                // Generate image with convolution kernels
+                int fullSize = 2 * size + 1 + 1;    // Full size of kernel
+                psImage *convKernels = psImageAlloc(5 * fullSize - 1, 5 * fullSize - 1, PS_TYPE_F32);
+                psImageInit(convKernels, NAN);
+                for (int j = -2; j <= 2; j++) {
+                    for (int i = -2; i <= 2; i++) {
+                        psImage *kernel = pmSubtractionKernelImage(solution, kernels, (float)i / 2.0,
+                                                                   (float)j / 2.0); // Image of the kernel
+                        if (!kernel) {
+                            psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+                            psFree(convKernels);
+                            goto ERROR;
+                        }
+
+                        if (psImageOverlaySection(convKernels, kernel, (i + 2) * fullSize,
+                                                  (j + 2) * fullSize, "=") == 0) {
+                            psError(PS_ERR_UNKNOWN, false, "Unable to overlay kernel image.");
+                            psFree(kernel);
+                            psFree(convKernels);
+                            goto ERROR;
+                        }
+                        psFree(kernel);
+                    }
+                }
+
+                psString comment = NULL; // Comment for metadata
+                psStringAppend(&comment, "Subtraction kernel for region %s", regionString);
+                psMetadataAddImage(convolved->analysis, PS_LIST_TAIL, "SUBTRACTION.KERNEL.IMAGE",
+                                   PS_META_DUPLICATE_OK, comment, convKernels);
+                psFree(comment);
+                psFree(convKernels);
+            }
+
+#ifdef TESTING
+            {
+                // Generate images of the kernel components
+                psMetadata *header = psMetadataAlloc(); // Header
+                for (int i = 0; i < solution->n; i++) {
+                    psString name = NULL;       // Header keyword
+                    psStringAppend(&name, "SOLN%04d", i);
+                    psMetadataAddF64(header, PS_LIST_TAIL, name, 0, NULL, solution->data.F64[i]);
+                    psFree(name);
+                }
+                psArray *kernelImages = pmSubtractionKernelSolutions(solution, kernels, 0.0, 0.0);
+                psFits *kernelFile = psFitsOpen("kernels.fits", "w");
+                (void)psFitsWriteImageCube(kernelFile, header, kernelImages, NULL);
+                psFitsClose(kernelFile);
+                psFree(kernelImages);
+                psFree(header);
+            }
+#endif
+
+            memCheck("diag outputs");
+
+            psTrace("psModules.imcombine", 2, "Convolving...\n");
+            if (!pmSubtractionConvolve(&convolved->image, &convolved->weight, &convolved->mask,
+                                       reference->image, reference->weight, subMask, maskBlank, region,
+                                       solution, kernels)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to convolve reference image.");
+                goto ERROR;
+            }
+
+            // Put the solution on the metadata
+            {
+                psString comment = NULL; // Comment for metadata
+                psStringAppend(&comment, "Subtraction solution for region %s", regionString);
+                psMetadataAddVector(convolved->analysis, PS_LIST_TAIL, "SUBTRACTION.SOLUTION",
+                                    PS_META_DUPLICATE_OK, comment, solution);
+                psFree(comment);
+                if (region) {
+                    psMetadataAddPtr(convolved->analysis, PS_LIST_TAIL, "SUBTRACTION.REGION",
+                                     PS_META_DUPLICATE_OK | PS_DATA_REGION, comment, region);
+                } else {
+                    region = psRegionAlloc(0, numCols, 0, numRows);
+                    psMetadataAddPtr(convolved->analysis, PS_LIST_TAIL, "SUBTRACTION.REGION",
+                                     PS_META_DUPLICATE_OK | PS_DATA_REGION, comment, region);
+                    psFree(region);
+                    region = NULL;
+                }
+            }
+
+            psFree(solution);
+            solution = NULL;
+
+            // There is data in the readout now
+            convolved->data_exists = true;
+            if (convolved->parent) {
+                convolved->parent->data_exists = true;
+                convolved->parent->parent->data_exists = true;
+            }
+        }
+    }
+    psFree(region);
+    region = NULL;
+    psFree(regionString);
+    regionString = NULL;
+    psFree(subMask);
+    subMask = NULL;
+
+    if (!pmSubtractionBorder(convolved->image, convolved->weight, convolved->mask, kernels, maskBlank)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set border of convolved image.");
+        goto ERROR;
+    }
+    psFree(kernels);
+
+    memCheck("convolution");
+
+
+    return true;
+
+ERROR:
+    psFree(region);
+    psFree(regionString);
+    psFree(subMask);
+    psFree(kernels);
+    psFree(stamps);
+    psFree(solution);
+    return false;
+}
Index: /branches/eam_branch_20070817/psModules/src/imcombine/pmSubtractionMatch.h
===================================================================
--- /branches/eam_branch_20070817/psModules/src/imcombine/pmSubtractionMatch.h	(revision 14637)
+++ /branches/eam_branch_20070817/psModules/src/imcombine/pmSubtractionMatch.h	(revision 14637)
@@ -0,0 +1,37 @@
+#ifndef PM_SUBTRACTION_MATCH_H
+#define PM_SUBTRACTION_MATCH_H
+
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmSubtractionKernels.h"
+
+
+bool pmSubtractionMatch(pmReadout *convolved, ///< Output convolved data
+                        const pmReadout *reference, ///< Reference data
+                        const pmReadout *input, ///< Input data
+                        // Stamp parameters
+                        int footprint,  ///< Stamp half-size
+                        float regionSize, ///< Typical size of iso-kernel regions
+                        float stampSpacing, ///< Typical spacing between stamps
+                        float threshold, ///< Threshold for stamps
+                        const char *stampsName, ///< Filename for stamps
+                        float targetWidth, ///< Width of PSF for simulated target
+                        // Kernel parameters
+                        pmSubtractionKernelsType type, ///< Kernel type
+                        int size,       ///< Kernel half-size
+                        int order,      ///< Spatial polynomial order
+                        const psVector *widths, ///< ISIS Gaussian widths
+                        const psVector *orders, ///< ISIS Polynomial orders
+                        int inner,      ///< Inner radius for various kernel types
+                        int ringsOrder, ///< RINGS polynomial order
+                        int binning,    ///< SPAM kernel binning
+                        // Operational parameters
+                        int iter,       ///< Rejection iterations
+                        float rej,      ///< Rejection threshold
+                        psMaskType maskBad, ///< Value to mask
+                        psMaskType maskBlank ///< Mask for blank region
+    );
+
+#endif
Index: /branches/eam_branch_20070817/psModules/test/camera/tap_pmFPAReadWrite.c
===================================================================
--- /branches/eam_branch_20070817/psModules/test/camera/tap_pmFPAReadWrite.c	(revision 14637)
+++ /branches/eam_branch_20070817/psModules/test/camera/tap_pmFPAReadWrite.c	(revision 14637)
@@ -0,0 +1,1259 @@
+#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, "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, "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, "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(405);
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmCellWrite(): tests
+    // Verify pmCellWrite() with NULL pmCell arg
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWrite(NULL, fitsFileW, NULL, false), "pmCellWrite() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Verify pmCellWrite() with NULL pmCell arg
+    // XXXX: Big problem: Without the next code, everything else fails.  Why?
+    if (1) {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWrite(NULL, fitsFileW, NULL, false), "pmCellWrite() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWrite() with NULL pmFits arg
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmCellWrite(cell, NULL, NULL, false), "pmCellWrite() returned FALSE with NULL psFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWrite() with acceptable input params
+    // We first write a FITS file with the pmCellWrite(), then we read it and verify.
+    // First call pmCellWrite()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        //  Use pmCellWrite() to write image data to the FITS file
+        bool rc = pmCellWrite(cell, fitsFileW, NULL, false);
+        ok(rc, "pmCellWrite() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellRead() tests 
+    // Verify pmCellRead() with NULL pmCell param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmCellRead(NULL, fitsFileR, NULL), "pmCellRead() returned FALSE with NULL pmCell param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellRead() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(!pmCellRead(cell, NULL, NULL), "pmCellRead() returned FALSE with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellRead() with acceptable data (using the FITS file created above)
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        // Free the existing cell hdu image data (so we can verify that pmCellRead() actually reads the data
+        psFree(cell->hdu->images);
+        cell->hdu->images = NULL;
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+
+        rc = pmCellRead(cell, fitsFileR, NULL);
+        ok(rc, "pmCellRead() returned TRUE");
+        for (int k = 0 ; k < cell->hdu->images->n ; k++) {
+            bool errorFlag = false;
+            psImage *img = cell->hdu->images->data[k];
+            for (int i = 0 ; i < img->numRows ; i++) {
+                for (int j = 0 ; j < img->numCols ; j++) {
+                    if (((float) (BASE_IMAGE+k)) != img->data.F32[i][j]) {
+                        diag("TEST ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j,
+                              img->data.F32[i][j], ((float) (BASE_IMAGE+k)));
+                        errorFlag = true;
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmCellWrite()/pmCellRead() properly set the image data (image %d)", k);
+        }
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmCellWriteWeight(): tests
+    // Verify pmCellWriteWeight() with NULL pmCell arg
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWriteWeight(NULL, fitsFileW, NULL, false), "pmCellWriteWeight() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteWeight() with NULL pmFits arg
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmCellWriteWeight(cell, NULL, NULL, false), "pmCellWriteWeight() returned FALSE with NULL psFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteWeight() with acceptable input params
+    // We first write a FITS file with the pmCellWriteWeight(), then we read it and verify.
+    // First call pmCellWriteWeight()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        //  Use pmCellWriteWeight() to write weight data to the FITS file
+        bool rc = pmCellWriteWeight(cell, fitsFileW, NULL, false);
+        ok(rc, "pmCellWriteWeight() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellReadWeight() tests 
+    // Verify pmCellReadWeight() with NULL pmCell param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmCellReadWeight(NULL, fitsFileR, NULL), "pmCellReadWeight() returned FALSE with NULL pmCell param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(!pmCellReadWeight(cell, NULL, NULL), "pmCellReadWeight() returned FALSE with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadWeight() with acceptable data (using the FITS file created above)
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        // Free the existing cell hdu weight data (so we can verify that pmCellReadWeight() actually reads the data
+        psFree(cell->hdu->weights);
+        cell->hdu->weights = NULL;
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+
+        rc = pmCellReadWeight(cell, fitsFileR, NULL);
+        ok(rc, "pmCellReadWeight() returned TRUE");
+        for (int k = 0 ; k < cell->hdu->weights->n ; k++) {
+            bool errorFlag = false;
+            psImage *msk = cell->hdu->weights->data[k];
+            for (int i = 0 ; i < msk->numRows ; i++) {
+                for (int j = 0 ; j < msk->numCols ; j++) {
+                    if (((float) (BASE_WEIGHT+k)) != msk->data.F32[i][j]) {
+                        diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                              msk->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                        errorFlag = true;
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmCellWriteWeight()/pmCellReadWeight() properly set the weight data (image %d)", k);
+        }
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmCellWriteMask(): tests
+    // Verify pmCellWriteMask() with NULL pmCell arg
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWriteMask(NULL, fitsFileW, NULL, false), "pmCellWriteMask() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteMask() with NULL pmFits arg
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmCellWriteMask(cell, NULL, NULL, false), "pmCellWriteMask() returned FALSE with NULL psFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteMask() with acceptable input params
+    // We first write a FITS file with the pmCellWriteMask(), then we read it and verify.
+    // First call pmCellWriteMask()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        //  Use pmCellWriteMask() to write mask data to the FITS file
+        bool rc = pmCellWriteMask(cell, fitsFileW, NULL, false);
+        ok(rc, "pmCellWriteMask() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellReadMask() tests 
+    // Verify pmCellReadMask() with NULL pmCell param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmCellReadMask(NULL, fitsFileR, NULL), "pmCellReadMask() returned FALSE with NULL pmCell param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(!pmCellReadMask(cell, NULL, NULL), "pmCellReadMask() returned FALSE with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadMask() with acceptable data (using the FITS file created above)
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        // Free the existing cell hdu mask data (so we can verify that pmCellReadMask() actually reads the data
+        psFree(cell->hdu->masks);
+        cell->hdu->masks = NULL;
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+
+        rc = pmCellReadMask(cell, fitsFileR, NULL);
+        ok(rc, "pmCellReadMask() returned TRUE");
+        for (int k = 0 ; k < cell->hdu->masks->n ; k++) {
+            bool errorFlag = false;
+            psImage *msk = cell->hdu->masks->data[k];
+            for (int i = 0 ; i < msk->numRows ; i++) {
+                for (int j = 0 ; j < msk->numCols ; j++) {
+                    if (0) {
+                        if (((float) (BASE_WEIGHT+k)) != msk->data.F32[i][j]) {
+                            diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  msk->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                            errorFlag = true;
+                        }
+		    }
+                    if (1) {
+                        if (((BASE_MASK+k)) != msk->data.U8[i][j]) {
+                            diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  msk->data.F32[i][j], ((float) (BASE_MASK+k)));
+                            errorFlag = true;
+                        }
+		    }
+    		}
+	    }
+            ok(!errorFlag, "pmCellWriteMask()/pmCellReadMask() properly set the mask data (image %d)", k);
+        }
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmChipWrite() tests
+    // Verify pmChipWrite() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmChipWrite(NULL, fitsFileW, NULL, false, true), "pmChipWrite() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWrite() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(!pmChipWrite(chip, NULL, NULL, false, true), "pmChipWrite() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWrite() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+
+        //  Use pmChipWrite() to write image data to the FITS file
+        bool rc = pmChipWrite(chip, fitsFileW, NULL, false, true);
+        ok(rc, "pmChipWrite() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipRead() tests
+    // Verify pmChipRead() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmChipRead(NULL, fitsFileR, NULL), "pmChipRead() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipRead() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(!pmChipRead(chip, NULL, NULL), "pmChipRead() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipRead() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmChipRead() actually reads the data from file
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            myFreeCell(cell);
+            cell = NULL;
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmChipRead(chip, fitsFileR, NULL);
+        ok(rc, "pmChipRead() returned TRUE");
+        bool errorFlag = false;
+        // XXX: chipID should be cellID
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            if (VERBOSE) printf("Reading cell %d\n", chipID);
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            for (int k = 0 ; k < cell->hdu->images->n ; k++) {
+                if (VERBOSE) printf("Cool: image %d\n", k);
+                psImage *img = cell->hdu->images->data[k];
+                for (int i = 0 ; i < img->numRows ; i++) {
+                    for (int j = 0 ; j < img->numCols ; j++) {
+                        if (((float) (BASE_IMAGE+k)) != img->data.F32[i][j]) {
+                            diag("TEST ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  img->data.F32[i][j], ((float) (BASE_IMAGE+k)));
+                            errorFlag = true;
+			}
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmChipWrite()/pmChipRead() properly set the image data (cell %d)", chipID);
+	}
+
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipWriteWeight() tests
+    // Verify pmChipWriteWeight() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmChipWriteWeight(NULL, fitsFileW, NULL, false, true), "pmChipWriteWeight() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWriteWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(!pmChipWriteWeight(chip, NULL, NULL, false, true), "pmChipWriteWeight() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWriteWeight() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+
+        //  Use pmChipWriteWeight() to write image data to the FITS file
+        bool rc = pmChipWriteWeight(chip, fitsFileW, NULL, false, true);
+        ok(rc, "pmChipWriteWeight() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipReadMask() tests
+    // Verify pmChipReadMask() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmChipReadMask(NULL, fitsFileR, NULL), "pmChipReadMask() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(!pmChipReadMask(chip, NULL, NULL), "pmChipReadMask() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmChipReadMask() actually reads the data from file
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            myFreeCell(cell);
+            cell = NULL;
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmChipReadMask(chip, fitsFileR, NULL);
+        ok(rc, "pmChipReadMask() returned TRUE");
+        bool errorFlag = false;
+        // XXX: chipID should be cellID
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            if (VERBOSE) printf("Reading cell %d\n", chipID);
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            for (int k = 0 ; k < cell->hdu->weights->n ; k++) {
+                if (VERBOSE) printf("Cool: image %d\n", k);
+                psImage *wgt = cell->hdu->weights->data[k];
+                for (int i = 0 ; i < wgt->numRows ; i++) {
+                    for (int j = 0 ; j < wgt->numCols ; j++) {
+                        if (((float) (BASE_WEIGHT+k)) != wgt->data.F32[i][j]) {
+                            diag("TEST ERROR: wgt[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  wgt->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                            errorFlag = true;
+			}
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmChipWriteWeight()/pmChipReadWeight() properly set the weight data (cell %d)", chipID);
+	}
+
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipReadMask() tests
+    // Verify pmChipReadMask() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmChipReadMask(NULL, fitsFileR, NULL), "pmChipReadMask() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(!pmChipReadMask(chip, NULL, NULL), "pmChipReadMask() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmChipReadMask() actually reads the data from file
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            myFreeCell(cell);
+            cell = NULL;
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmChipReadMask(chip, fitsFileR, NULL);
+        ok(rc, "pmChipReadMask() returned TRUE");
+        bool errorFlag = false;
+        // XXX: chipID should be cellID
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            if (VERBOSE) printf("Reading cell %d\n", chipID);
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            for (int k = 0 ; k < cell->hdu->masks->n ; k++) {
+                if (VERBOSE) printf("Cool: image %d\n", k);
+                psImage *msk = cell->hdu->masks->data[k];
+                for (int i = 0 ; i < msk->numRows ; i++) {
+                    for (int j = 0 ; j < msk->numCols ; j++) {
+                        if (((BASE_MASK+k)) != msk->data.U8[i][j]) {
+                            diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  msk->data.F32[i][j], ((float) (BASE_MASK+k)));
+                            errorFlag = true;
+			}
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmChipWriteMask()/pmChipReadMask() properly set the mask data (cell %d)", chipID);
+	}
+
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmFPAWrite() tests
+    // pmFPAWrite(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+    // Verify pmFPAWrite() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmFPAWrite(NULL, fitsFileW, NULL, false, true), "pmFPAWrite() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWrite() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(!pmFPAWrite(fpa, NULL, NULL, false, true), "pmFPAWrite() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWrite() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool rc = pmFPAWrite(fpa, fitsFileW, NULL, false, true);
+        ok(rc, "pmFPAWrite() returned TRUE");
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPARead() tests
+    // Verify pmFPARead() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmFPARead(NULL, fitsFileR, NULL), "pmFPARead() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPARead() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(!pmFPARead(fpa, NULL, NULL), "pmFPARead() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPARead() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmFPARead() actually reads the data from file
+        if (0) {
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                myFreeCell(cell);
+                cell = NULL;
+            }
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmFPARead(fpa, fitsFileR, NULL);
+        ok(rc, "pmFPARead() returned TRUE");
+        bool errorFlag = false;
+        // XXX: fpaID should be chipID
+        // XXX: chipID should be cellID
+        for (int fpaID = 0 ; fpaID < fpa->chips->n ; fpaID++) {
+            pmChip *chip = fpa->chips->data[fpaID];
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                if (VERBOSE) printf("Reading cell %d\n", chipID);
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                for (int k = 0 ; k < cell->hdu->images->n ; k++) {
+                    if (VERBOSE) printf("Cool: image %d\n", k);
+                    psImage *img = cell->hdu->images->data[k];
+                    for (int i = 0 ; i < img->numRows ; i++) {
+                        for (int j = 0 ; j < img->numCols ; j++) {
+                            if (((float) (BASE_IMAGE+k)) != img->data.F32[i][j]) {
+                                diag("TEST ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                      img->data.F32[i][j], ((float) (BASE_IMAGE+k)));
+                                errorFlag = true;
+        			}
+        		    }
+        		}
+        	    }
+                ok(!errorFlag, "pmFPAWrite()/pmFPARead() properly set the image data (chip %d, cell %d)", fpaID, chipID);
+    	    }
+	}
+
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmFPAWriteWeight() tests
+    // pmFPAWriteWeight(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+    // Verify pmFPAWriteWeight() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmFPAWriteWeight(NULL, fitsFileW, NULL, false, true), "pmFPAWriteWeight() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(!pmFPAWriteWeight(fpa, NULL, NULL, false, true), "pmFPAWriteWeight() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteWeight() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool rc = pmFPAWriteWeight(fpa, fitsFileW, NULL, false, true);
+        ok(rc, "pmFPAWriteWeight() returned TRUE");
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAReadWeight() tests
+    // Verify pmFPAReadWeight() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmFPARead(NULL, fitsFileR, NULL), "pmFPAReadWeight() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(!pmFPARead(fpa, NULL, NULL), "pmFPAReadWeight() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadWeight() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmFPAReadWeight() actually reads the data from file
+        if (0) {
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                myFreeCell(cell);
+                cell = NULL;
+            }
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmFPARead(fpa, fitsFileR, NULL);
+        ok(rc, "pmFPAReadWeight() returned TRUE");
+        bool errorFlag = false;
+        // XXX: fpaID should be chipID
+        // XXX: chipID should be cellID
+        for (int fpaID = 0 ; fpaID < fpa->chips->n ; fpaID++) {
+            pmChip *chip = fpa->chips->data[fpaID];
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                if (VERBOSE) printf("Reading cell %d\n", chipID);
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                for (int k = 0 ; k < cell->hdu->weights->n ; k++) {
+                    if (VERBOSE) printf("Cool: image %d\n", k);
+                    psImage *wgt = cell->hdu->weights->data[k];
+                    for (int i = 0 ; i < wgt->numRows ; i++) {
+                        for (int j = 0 ; j < wgt->numCols ; j++) {
+                            if (((float) (BASE_WEIGHT+k)) != wgt->data.F32[i][j]) {
+                                diag("TEST ERROR: wgt[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                      wgt->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                                errorFlag = true;
+        			}
+        		    }
+        		}
+        	    }
+                ok(!errorFlag, "pmFPAWriteWeight()/pmFPAReadWeight() properly set the image data (chip %d, cell %d)", fpaID, chipID);
+    	    }
+	}
+
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmFPAWriteMask() tests
+    // pmFPAWriteMask(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+    // Verify pmFPAWriteMask() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmFPAWriteMask(NULL, fitsFileW, NULL, false, true), "pmFPAWriteMask() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(!pmFPAWriteMask(fpa, NULL, NULL, false, true), "pmFPAWriteMask() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteMask() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool rc = pmFPAWriteMask(fpa, fitsFileW, NULL, false, true);
+        ok(rc, "pmFPAWriteMask() returned TRUE");
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAReadMask() tests
+    // Verify pmFPAReadMask() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmFPARead(NULL, fitsFileR, NULL), "pmFPAReadMask() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(!pmFPARead(fpa, NULL, NULL), "pmFPAReadMask() returned NULL with NULL pmFits param");
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadMask() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmFPAReadMask() actually reads the data from file
+        if (0) {
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                myFreeCell(cell);
+                cell = NULL;
+            }
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmFPARead(fpa, fitsFileR, NULL);
+        ok(rc, "pmFPAReadMask() returned TRUE");
+        bool errorFlag = false;
+        // XXX: fpaID should be chipID
+        // XXX: chipID should be cellID
+        for (int fpaID = 0 ; fpaID < fpa->chips->n ; fpaID++) {
+            pmChip *chip = fpa->chips->data[fpaID];
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                if (VERBOSE) printf("Reading cell %d\n", chipID);
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                for (int k = 0 ; k < cell->hdu->masks->n ; k++) {
+                    if (VERBOSE) printf("Cool: image %d\n", k);
+                    psImage *msk = cell->hdu->masks->data[k];
+                    for (int i = 0 ; i < msk->numRows ; i++) {
+                        for (int j = 0 ; j < msk->numCols ; j++) {
+                            if (((BASE_MASK+k)) != msk->data.U8[i][j]) {
+                                diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                      msk->data.F32[i][j], ((float) (BASE_MASK+k)));
+                                errorFlag = true;
+        			}
+        		    }
+        		}
+        	    }
+                ok(!errorFlag, "pmFPAWriteMask()/pmFPAReadMask() properly set the image data (chip %d, cell %d)", fpaID, chipID);
+    	    }
+	}
+
+        psFitsClose(fitsFileR);
+        myFreeFPA(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
+
+//HERE
Index: /branches/eam_branch_20070817/psModules/test/camera/tap_pmHDU.c
===================================================================
--- /branches/eam_branch_20070817/psModules/test/camera/tap_pmHDU.c	(revision 14637)
+++ /branches/eam_branch_20070817/psModules/test/camera/tap_pmHDU.c	(revision 14637)
@@ -0,0 +1,544 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/*
+    The following psModule functions are tested:
+        pmHDUAlloc()
+        pmHDUReadHeader()
+        pmHDURead()
+        pmHDUReadMask()
+        pmHDUReadWeight()
+        pmHDUWrite()
+        pmHDUWriteMask()
+        pmHDUWriteWeight()
+*/
+
+#define NUM_HDUS 8
+char *fitsFilename = "tmp.fits";
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", 0);
+    plan_tests(159);
+
+    // ----------------------------------------------------------------------
+    // pmHDUAlloc() tests
+    // Test pmHDUAlloc() with a NULL extname
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc(NULL);
+        ok(hdu, "pmHDUAlloc(NULL) returned non-NULL");
+        skip_start(!hdu, 7, "Skipping tests because pmHDUAlloc(NULL) returned NULL");
+        ok(hdu->blankPHU == true, "pmHDUAlloc(NULL) set hdu->blankPHU correctly");
+        ok(hdu->extname == NULL, "pmHDUAlloc(NULL) set hdu->extname correctly");
+        ok(hdu->format == NULL, "pmHDUAlloc(NULL) set hdu->format correctly");
+        ok(hdu->header == NULL, "pmHDUAlloc(NULL) set hdu->header correctly");
+        ok(hdu->images == NULL, "pmHDUAlloc(NULL) set hdu->images correctly");
+        ok(hdu->weights == NULL, "pmHDUAlloc(NULL) set hdu->weights correctly");
+        ok(hdu->masks == NULL, "pmHDUAlloc(NULL) set hdu->masks correctly");
+        psFree(hdu);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUAlloc() with a non-NULL extname
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("ext-0");
+        ok(hdu, "pmHDUAlloc(extname) returned non-NULL");
+        skip_start(!hdu, 7, "Skipping tests because pmHDUAlloc(extname) returned NULL");
+        ok(hdu->blankPHU == false, "pmHDUAlloc(extname) set hdu->blankPHU correctly");
+        ok(0 == strcmp(hdu->extname, "ext-0"), "pmHDUAlloc(extname) set hdu->extname correctly");
+        ok(hdu->format == NULL, "pmHDUAlloc(extname) set hdu->format correctly");
+        ok(hdu->header == NULL, "pmHDUAlloc(extname) set hdu->header correctly");
+        ok(hdu->images == NULL, "pmHDUAlloc(extname) set hdu->images correctly");
+        ok(hdu->weights == NULL, "pmHDUAlloc(extname) set hdu->weights correctly");
+        ok(hdu->masks == NULL, "pmHDUAlloc(extname) set hdu->masks correctly");
+        psFree(hdu);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUWrite(), pmHDURead tests
+    // Test pmHDUWrite() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        // Create simple FITS file
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        psMetadata* header = psMetadataAlloc();
+        psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+        ok(psFitsWriteImage(fitsFile, header, image, 0, "extname"), "psFitsWriteImage() successful");
+        psFree(header);
+        psFree(image);
+        bool rc = pmHDUWrite(NULL, fitsFile);
+        ok(rc == false, "pmHDUWrite() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUWrite() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32, "psS32 Item", 0);
+        bool rc = pmConfigFileRead(&hdu->format, "data/camera0/format0.config", "Camera 0 Config Format");
+        ok(rc == true, "pmConfigFileRead() was successful");
+        rc = pmHDUWrite(hdu, NULL);
+        ok(rc == false, "pmHDUWrite() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDURead() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("data/sampleFitsFile.fits", "r");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDURead(NULL, fitsFile);
+        ok(rc == false, "pmHDURead() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDURead() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDURead(hdu, NULL);
+        ok(rc == false, "pmHDURead() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // 1. Create a simple FITS file
+    // 2. Create a simple pmHDU header
+    // 3. Use pmHDUWrite() to write that header to the FITS file
+    // 4. Close the FITS file
+    // 5. Read that fits file from disk
+    // 6. Create another pmHDU
+    // 7. Call pmHDURead()which will read the FITS data into hdu->images
+    // 8. Verify that hdu->images was set correctly.
+    // 
+    // XXX: Add code to delete .tmp00
+    // XXX: Should we try with multiple images in the psArray hdu->images?
+    #define BASE 20
+    {
+        psMemId id = psMemGetId();
+        // 1. Create a simple FITS file
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(fitsFileW != NULL, "psFitsOpen() was successful");
+        // 2. Create a simple pmHDU header
+        //    Allocate and format hdu->header
+        //    Set hdu->images
+        pmHDU *hdu = pmHDUAlloc("extname");
+        bool rc = pmConfigFileRead(&hdu->format, "data/camera0/format0.config", "Camera format 0");
+        ok(rc, "pmConfigFileRead() was successful");
+        hdu->images = psArrayAlloc(NUM_HDUS);
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            hdu->images->data[k] = psImageAlloc(4, 4, PS_TYPE_F32);
+            psImageInit(hdu->images->data[k], (float) (BASE+k));
+	}
+        // 3. Use pmHDUWrite() to write that header to the FITS file
+        rc = pmHDUWrite(hdu, fitsFileW);
+        ok(rc == true, "pmHDUWrite() returned TRUE");
+        // 4. Close the FITS file, free memory
+        psFree(hdu);
+        psFitsClose(fitsFileW);
+
+        // Now, try to read that FITS file from disk.
+        // 5. Read that fits file from disk
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(fitsFileR != NULL, "psFitsOpen() was successful");
+        // 6. Create another pmHDU
+        hdu = pmHDUAlloc("extname");
+        hdu->images = NULL;
+        // 7. Call pmHDURead()which will read the FITS data into hdu->images
+        rc = pmHDURead(hdu, fitsFileR);
+        ok(rc == true, "pmHDURead() returned TRUE");
+        ok(hdu->images != NULL, "pmHDURead() created hdu->images");
+        // 8. Verify that hdu->images was set correctly.
+        bool errorFlag = false;
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            psImage *img = (psImage *) hdu->images->data[k];
+            for (psS32 i = 0 ; i < img->numRows ; i++) {
+                for (psS32 j = 0 ; j < img->numCols ; j++) {
+                    if (((float) k+BASE) != img->data.F32[i][j]) {
+                        errorFlag = true;
+                        diag("TEST ERROR (image %d): img[%d][%d] is %f, should be %f\n",
+                              k, i, j, img->data.F32[i][j], ((float)k+BASE));
+        	    }
+            	}
+	    }
+	}
+        ok(!errorFlag, "pmHDURead() correctly returned the image data");
+        psFitsClose(fitsFileR);
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUWriteWeight(), pmHDUReadWeight() tests
+    // Test pmHDUWriteWeight() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        // Create simple FITS file
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        psMetadata* header = psMetadataAlloc();
+        psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+        ok(psFitsWriteImage(fitsFile, header, image, 0, "extname"), "psFitsWriteImage() successful");
+        psFree(header);
+        psFree(image);
+        bool rc = pmHDUWriteWeight(NULL, fitsFile);
+        ok(rc == false, "pmHDUWriteWeight() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUWriteWeight() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32, "psS32 Item", 0);
+        bool rc = pmConfigFileRead(&hdu->format, "data/camera0/format0.config", "Camera 0 Config Format");
+        ok(rc == true, "pmConfigFileRead() was successful");
+        rc = pmHDUWriteWeight(hdu, NULL);
+        ok(rc == false, "pmHDUWriteWeight    () returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadWeight() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("data/sampleFitsFile.fits", "r");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDUReadWeight(NULL, fitsFile);
+        ok(rc == false, "pmHDUReadWeight() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadWeight() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDUReadWeight(hdu, NULL);
+        ok(rc == false, "pmHDUReadWeight    () returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // 1. Create a simple FITS file
+    // 2. Create a simple pmHDU header
+    // 3. Use pmHDUWriteWeight() to write that header to the FITS file
+    // 4. Close the FITS file
+    // 5. Read that fits file from disk
+    // 6. Create another pmHDU
+    // 7. Call pmHDUReadWeight()which will read the FITS data into hdu->images
+    // 8. Verify that hdu->images was set correctly.
+    // 
+    // XXX: Add code to delete .tmp00
+    // XXX: Should we try with multiple images in the psArray hdu->images?
+    #define BASE 20
+    {
+        psMemId id = psMemGetId();
+        // 1. Create a simple FITS file
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(fitsFileW != NULL, "psFitsOpen() was successful");
+        // 2. Create a simple pmHDU header
+        //    Allocate and format hdu->header
+        //    Set hdu->images
+        pmHDU *hdu = pmHDUAlloc("extname");
+        bool rc = pmConfigFileRead(&hdu->format, "data/camera0/format0.config", "Camera format 0");
+        ok(rc, "pmConfigFileRead() was successful");
+        hdu->weights = psArrayAlloc(NUM_HDUS);
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            hdu->weights->data[k] = psImageAlloc(4, 4, PS_TYPE_F32);
+            psImageInit(hdu->weights->data[k], (float) (BASE+k));
+	}
+        // 3. Use pmHDUWriteWeight() to write that header to the FITS file
+        rc = pmHDUWriteWeight(hdu, fitsFileW);
+        ok(rc == true, "pmHDUWriteWeight() returned TRUE");
+        // 4. Close the FITS file, free memory
+        psFree(hdu);
+        psFitsClose(fitsFileW);
+
+        // Now, try to read that FITS file from disk.
+        // 5. Read that fits file from disk
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(fitsFileR != NULL, "psFitsOpen() was successful");
+        // 6. Create another pmHDU
+        hdu = pmHDUAlloc("extname");
+        hdu->weights = NULL;
+        // 7. Call pmHDUReadWeight()which will read the FITS data into hdu->weights
+        rc = pmHDUReadWeight(hdu, fitsFileR);
+        ok(rc == true, "pmHDUReadWeight() returned TRUE");
+        ok(hdu->weights != NULL, "pmHDUReadWeight() created hdu->weights");
+        // 8. Verify that hdu->weights was set correctly.
+        bool errorFlag = false;
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            psImage *img = (psImage *) hdu->weights->data[k];
+            for (psS32 i = 0 ; i < img->numRows ; i++) {
+                for (psS32 j = 0 ; j < img->numCols ; j++) {
+                    if (((float) k+BASE) != img->data.F32[i][j]) {
+                        errorFlag = true;
+                        diag("TEST ERROR (image %d): img[%d][%d] is %f, should be %f\n",
+                              k, i, j, img->data.F32[i][j], ((float)k+BASE));
+        	    }
+            	}
+	    }
+	}
+        ok(!errorFlag, "pmHDUReadWeight() correctly returned the image data");
+        psFitsClose(fitsFileR);
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUWriteMask(), pmHDUReadMask() tests
+    // Test pmHDUWriteMask() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        // Create simple FITS file
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        psMetadata* header = psMetadataAlloc();
+        psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+        ok(psFitsWriteImage(fitsFile, header, image, 0, "extname"), "psFitsWriteImage() successful");
+        psFree(header);
+        psFree(image);
+        bool rc = pmHDUWriteMask(NULL, fitsFile);
+        ok(rc == false, "pmHDUWriteMask() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUWriteMask() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32, "psS32 Item", 0);
+        bool rc = pmConfigFileRead(&hdu->format, "data/camera0/format0.config", "Camera 0 Config Format");
+        ok(rc == true, "pmConfigFileRead() was successful");
+        rc = pmHDUWriteMask(hdu, NULL);
+        ok(rc == false, "pmHDUWriteMask() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadMask() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("data/sampleFitsFile.fits", "r");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDUReadMask(NULL, fitsFile);
+        ok(rc == false, "pmHDUReadMask() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadMask() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDUReadMask(hdu, NULL);
+        ok(rc == false, "pmHDUReadMask() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // 1. Create a simple FITS file
+    // 2. Create a simple pmHDU header
+    // 3. Use pmHDUWriteMask() to write that header to the FITS file
+    // 4. Close the FITS file
+    // 5. Read that fits file from disk
+    // 6. Create another pmHDU
+    // 7. Call pmHDUReadMask()which will read the FITS data into hdu->images
+    // 8. Verify that hdu->images was set correctly.
+    // 
+    // XXX: Add code to delete .tmp00
+    // XXX: Should we try with multiple images in the psArray hdu->images?
+    #define BASE 20
+    {
+        psMemId id = psMemGetId();
+        // 1. Create a simple FITS file
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(fitsFileW != NULL, "psFitsOpen() was successful");
+        // 2. Create a simple pmHDU header
+        //    Allocate and format hdu->header
+        //    Set hdu->images
+        pmHDU *hdu = pmHDUAlloc("extname");
+        bool rc = pmConfigFileRead(&hdu->format, "data/camera0/format0.config", "Camera format 0");
+        ok(rc, "pmConfigFileRead() was successful");
+        hdu->masks = psArrayAlloc(NUM_HDUS);
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            hdu->masks->data[k] = psImageAlloc(4, 4, PS_TYPE_F32);
+            psImageInit(hdu->masks->data[k], (float) (BASE+k));
+	}
+        // 3. Use pmHDUWriteMask() to write that header to the FITS file
+        rc = pmHDUWriteMask(hdu, fitsFileW);
+        ok(rc == true, "pmHDUWriteMask() returned TRUE");
+        // 4. Close the FITS file, free memory
+        psFree(hdu);
+        psFitsClose(fitsFileW);
+
+        // Now, try to read that FITS file from disk.
+        // 5. Read that fits file from disk
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(fitsFileR != NULL, "psFitsOpen() was successful");
+        // 6. Create another pmHDU
+        hdu = pmHDUAlloc("extname");
+        hdu->masks = NULL;
+        // 7. Call pmHDUReadMask()which will read the FITS data into hdu->masks
+        rc = pmHDUReadMask(hdu, fitsFileR);
+        ok(rc == true, "pmHDUReadMask() returned TRUE");
+        ok(hdu->masks != NULL, "pmHDUReadMask() created hdu->masks");
+        // 8. Verify that hdu->masks was set correctly.
+        bool errorFlag = false;
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            psImage *img = (psImage *) hdu->masks->data[k];
+            for (psS32 i = 0 ; i < img->numRows ; i++) {
+                for (psS32 j = 0 ; j < img->numCols ; j++) {
+                    if (((float) k+BASE) != img->data.F32[i][j]) {
+                        errorFlag = true;
+                        diag("TEST ERROR (image %d): img[%d][%d] is %f, should be %f\n",
+                              k, i, j, img->data.F32[i][j], ((float)k+BASE));
+        	    }
+            	}
+	    }
+	}
+        ok(!errorFlag, "pmHDUReadMask() correctly returned the image data");
+        psFitsClose(fitsFileR);
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUReadHeader(), pmHDUWriteHeader() tests
+    // Test pmHDUReadHeader() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("data/sampleFitsFile.fits", "r");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDUReadHeader(NULL, fitsFile);
+        ok(rc == false, "pmHDUReadHeader() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadHeader() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDUReadHeader(hdu, NULL);
+        ok(rc == false, "pmHDUReadHeader() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadHeader() and pmHDUWriteHeader()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        char extname[80];
+        for (int lcv = 0; lcv < NUM_HDUS; lcv++) {
+            snprintf(extname, 80, "ext-%d", lcv);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            hdu->header = psMetadataAlloc();
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32,
+                         "psS32 Item", (psS32)lcv);
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYFLT", PS_DATA_F32,
+                         "psF32 Item", (float)(1.0f/(float)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYDBL", PS_DATA_F64,
+                         "psF64 Item", (double)(1.0/(double)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYBOOL", PS_DATA_BOOL,
+                         "psBool Item", (lcv%2 == 0));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYSTR", PS_DATA_STRING,
+                         "String Item", extname);
+            bool rc = pmConfigFileRead(&hdu->format, "data/camera0/format0.config", "Camera 0 Config Format");
+            ok(rc == true, "pmConfigFileRead() was successful");
+            rc = pmHDUWrite(hdu, fitsFileW);
+            ok(rc == true, "pmHDUWrite() successfully wrote the header");
+            psFree(hdu);
+        }
+        psFitsClose(fitsFileW);
+
+        psFits* fitsFileR = psFitsOpen(fitsFilename, "r");
+        ok(fitsFileR != NULL, "psFitsOpen returned non-NULL on existing file");
+        int numHDUs = psFitsGetSize(fitsFileR);
+        ok(numHDUs == NUM_HDUS, "The test FITS file has %d HDUs", numHDUs);
+
+        for (int hdunum = 0; hdunum < NUM_HDUS; hdunum++)
+        {
+            char extname[80];
+            snprintf(extname,80, "ext-%d", hdunum);
+            psFitsMoveExtNum(fitsFileR, hdunum, false);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            bool rc = pmHDUReadHeader(hdu, fitsFileR);
+            ok(rc == true, "pmHDUReadHeader() returned TRUE");
+            ok(hdu->header != NULL && psMemCheckMetadata(hdu->header),
+              "pmHDUReadHeader() correctly returned the hdu->header member");
+
+            psS32 intItem = psMetadataLookupS32(NULL, hdu->header, "MYINT");
+            ok(intItem == hdunum, "Retrieved psS32 metadata item from file");
+
+            psF32 fltItem = psMetadataLookupF32(NULL, hdu->header, "MYFLT");
+            ok(fabsf(fltItem - 1.0f/(float)(1+hdunum)) <= FLT_EPSILON,
+               "Retrieved psF32 metadata item from file.  Got %f vs %f",
+               fltItem,1.0f/(float)(1+hdunum));
+
+            psF64 dblItem = psMetadataLookupF64(NULL, hdu->header, "MYDBL");
+            ok(abs(dblItem - 1.0/(double)(1+hdunum)) <= DBL_EPSILON,
+               "Retrieved psF64 metadata item from file.  Got %g vs %g",
+               dblItem, 1.0/(double)(1+hdunum));
+
+            psMetadataItem* boolItem = psMetadataLookup(hdu->header, "MYBOOL");
+            ok(boolItem != NULL && boolItem->type == PS_DATA_BOOL,
+               "Retrieved psBool metadata item from file");
+
+            psString strItem = psMetadataLookupStr(NULL, hdu->header, "MYSTR");
+            ok(strItem != NULL && strncmp(strItem,extname,strlen(extname)) == 0,
+               "Retrieved string metadata item from file.  Got '%s' vs '%s' (%d)",
+               strItem,extname,strlen(extname));
+
+            psFree(hdu);
+        }
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
