Index: trunk/psModules/src/config/pmConfigMask.c
===================================================================
--- trunk/psModules/src/config/pmConfigMask.c	(revision 19870)
+++ trunk/psModules/src/config/pmConfigMask.c	(revision 21183)
@@ -9,103 +9,79 @@
 #include "pmConfigMask.h"
 
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Private functions
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// maskSetValues examine named mask values and set the bits for maskValue and markValue.
-// Ensures that the below-named bad mask values are set, and calculates the mask value to catch them all
-// Ensure that the below-named other mask values are set (to 0x00 if necessary)
-
-// List of mask names for "bad" (i.e., mask me please) pixels
-static const char *badMaskNames[] = { "DETECTOR", // Something is wrong with the detector
-                                      "DARK", // Pixel doesn't dark-subtract properly
-                                      "FLAT", // Pixel doesn't flat-field properly
-                                      "BLANK", // Pixel doesn't contain valid data
-                                      "RANGE",// Pixel is out-of-range of linearity
-                                      "SAT",  // Pixel is saturated
-                                      // "LOW",  // Pixel is low
-                                      // "CONV", // Pixel is bad after convolution with a bad pixel
-                                      "BAD", // Pixel is low
-                                      "BAD.WARP", // Pixel is bad after convolution with a bad pixel
-                                      "CR",   // Pixel contains a cosmic ray
-                                      "GHOST",// Pixel contains an optical ghost
-                                      NULL // End marker
+static pmConfigMaskInfo masks[] = {
+    { "DETECTOR",  NULL,       0x00, true  },	// Something is wrong with the detector
+    { "DARK",      "DETECTOR", 0x00, true  },	// Pixel doesn't dark-subtract properly
+    { "FLAT",  	   "DETECTOR", 0x01, true  },	// Pixel doesn't flat-field properly
+    { "BLANK", 	   "DETECTOR", 0x01, true  },	// Pixel doesn't contain valid data
+    { "RANGE",     NULL,       0x00, true  },	// Pixel is out-of-range of linearity
+    { "SAT",       "RANGE",    0x01, true  },	// Pixel is saturated
+    { "BAD",       "RANGE",    0x01, true  },	// Pixel is low
+    { "BAD.WARP",  NULL,       0x01, true  },	// Pixel is bad after convolution with a bad pixel
+    { "CR",        NULL,       0x00, true  },	// Pixel contains a cosmic ray
+    { "GHOST",     NULL,       0x00, true  },	// Pixel contains an optical ghost
+    { "POOR.WARP", NULL,       0x00, false },	// Pixel is poor after convolution with a bad pixel
+    // "LOW"  Pixel is low
+    // "CONV" Pixel is bad after convolution with a bad pixel
 };
-// Fallback names in case a bad mask name is not defined
-static const char *fallbackMaskNames[] = { NULL, // DETECTOR
-                                           "DETECTOR", // DARK
-                                           "DETECTOR", // FLAT
-                                           "DETECTOR", // BLANK
-                                           NULL, // RANGE
-                                           "RANGE", // SAT
-                                           "RANGE", // LOW
-                                           NULL, // CONV
-                                           NULL, // CR
-                                           NULL, // GHOST
-};
-// Default values in case a bad mask name and its fallback is not defined
-static const psMaskType defaultMask[] = { 0x00, // DETECTOR
-                                          0x00, // DARK
-                                          0x01, // FLAT
-                                          0x01, // BLANK
-                                          0x00, // RANGE
-                                          0x01, // SAT
-                                          0x01, // LOW
-                                          0x01, // CONV
-                                          0x00, // CR
-                                          0x00  // GHOST
-};
-// Other mask names to ensure exist; these shouldn't be combined in the MASK.VALUE
-static const char *otherMaskNames[] = { // "POOR", // Pixel is poor after convolution with a bad pixel
-                                        "POOR.WARP", // Pixel is poor after convolution with a bad pixel
-                                        NULL // End marker
-};
-
-static bool maskSetValues(psMaskType *outMaskValue, // Value of MASK.VALUE, returned
-                          psMaskType *outMarkValue, // Value of MARK.VALUE, returned
-                          psMetadata *source  // Source of mask bits
-                          )
+
+// The functions in this file do not force the recipe or header values to be stored as the same
+// type as psImageMaskType : they only check that the given values will fit in the space
+// provided by psImageMaskType.  This should allow some backwards compatibility (old 8-bit
+// masks will work with a 16-bit system), but will catch unhandled conflicts (trying to fit 16
+// bits in 8-bits of space).
+
+// XXX this file does not have psError vs psWarning worked out correctly.  some of the 
+// failure modes should result in errors, not just warnings.
+
+// pmConfigMaskSetInMetadata examines named mask values and set the bits for maskValue and
+// markValue.  Ensures that the below-named mask values are set, and calculates the mask value
+// to catch all of the mask values marked as 'bad'.  Supplies the fallback name if the primary
+// name is not found, or the default values if the fallback name is not found.
+
+bool pmConfigMaskSetInMetadata(psImageMaskType *outMaskValue, // Value of MASK.VALUE, returned
+			       psImageMaskType *outMarkValue, // Value of MARK.VALUE, returned
+			       psMetadata *source  // Source of mask bits
+    )
 {
     PS_ASSERT_METADATA_NON_NULL(source, false);
-
+    
     // Ensure all the bad mask names exist, and set the value to catch all bad pixels
-    psMaskType maskValue = 0;           // Value to mask to catch all the bad pixels
-    for (int i = 0; badMaskNames[i]; i++) {
-        const char *name = badMaskNames[i]; // Name for mask
-        const char *fallback = fallbackMaskNames[i]; // Fallback for mask
-
+    psImageMaskType maskValue = 0;           // Value to mask to catch all the bad pixels
+    psImageMaskType allMasks = 0;	     // Value to mask to catch all masked bits (to set MARK)
+
+    int nMasks = sizeof (masks) / sizeof (pmConfigMaskInfo);
+
+    for (int i = 0; i < nMasks; i++) {
         bool mdok;                      // Status of MD lookup
-        psMaskType value = psMetadataLookupU8(&mdok, source, name); // Value of mask
+        psImageMaskType value = psMetadataLookupImageMaskFromGeneric(&mdok, source, masks[i].badMaskName); // Value of mask
+	if (!mdok) {
+	    psWarning ("problem with mask value %s\n", masks[i].badMaskName);
+	}
+
         if (!value) {
-            if (fallback) {
-                value = psMetadataLookupU8(&mdok, source, fallback);
-            }
-            if (!value) {
-                value = defaultMask[i];
-            }
-            psMetadataAddU8(source, PS_LIST_TAIL, name, PS_META_REPLACE, NULL, value);
-        }
-        maskValue |= value;
-    }
-
-    // Ensure all the other mask names exist
-    for (int i = 0; otherMaskNames[i]; i++) {
-        const char *name = otherMaskNames[i]; // Name for mask
-        bool mdok;                      // Status of MD lookup
-        psMaskType value = psMetadataLookupU8(&mdok, source, name); // Value of mask
-        if (!value) {
-            psMetadataAddU8(source, PS_LIST_TAIL, name, PS_META_REPLACE, NULL, 0x00);
-        }
+	    if (masks[i].fallbackName) {
+		value = psMetadataLookupImageMaskFromGeneric(&mdok, source, masks[i].fallbackName);
+	    }
+	    if (!value) {
+		value = masks[i].defaultMaskValue;
+	    }
+	    psMetadataAddImageMask(source, PS_LIST_TAIL, masks[i].badMaskName, PS_META_REPLACE, NULL, value);
+        }
+	if (masks[i].isBad) {
+	    maskValue |= value;
+	}
+	allMasks |= value;
     }
 
     // search for an unset bit to use for MARK:
-    psMaskType markValue = 0x80;
-
-    int nBits = sizeof(psMaskType) * 8;
+    psImageMaskType markValue = 0x00;
+    psImageMaskType markTrial = 0x01;
+
+    int nBits = sizeof(psImageMaskType) * 8;
     for (int i = 0; !markValue && (i < nBits); i++) {
-        if (maskValue & markValue) {
-            markValue >>= 1;
+        if (allMasks & markTrial) {
+            markTrial <<= 1;
         } else {
-            markValue = markValue;
+            markValue = markTrial;
         }
     }
@@ -116,6 +92,6 @@
 
     // update the list with the results
-    psMetadataAddU8(source, PS_LIST_TAIL, "MASK.VALUE", PS_META_REPLACE, NULL, maskValue);
-    psMetadataAddU8(source, PS_LIST_TAIL, "MARK.VALUE", PS_META_REPLACE, NULL, markValue);
+    psMetadataAddImageMask(source, PS_LIST_TAIL, "MASK.VALUE", PS_META_REPLACE, NULL, maskValue);
+    psMetadataAddImageMask(source, PS_LIST_TAIL, "MARK.VALUE", PS_META_REPLACE, NULL, markValue);
 
     if (outMaskValue) {
@@ -130,9 +106,9 @@
 
 // Get a mask value by name(s)
-static psMaskType maskGet(psMetadata *source, // Source of masks
-                          const char *masks // Mask values to get
-                          )
-{
-    psMaskType mask = 0;                // Mask value, to return
+psImageMaskType pmConfigMaskGetFromMetadata(psMetadata *source, // Source of masks
+					    const char *masks // Mask values to get
+    )
+{
+    psImageMaskType mask = 0;                // Mask value, to return
 
     psArray *names = psStringSplitArray(masks, " ,;", false); // Array of symbolic names
@@ -140,13 +116,13 @@
         const char *name = names->data[i]; // Symbolic name of interest
         bool mdok;                      // Status of MD lookup
-        psMaskType value = psMetadataLookupU8(&mdok, source, name);
+        psImageMaskType value = psMetadataLookupImageMaskFromGeneric(&mdok, source, name);
         if (!mdok) {
             // Try and generate the value if we can
             if (strcmp(name, "MASK.VALUE") == 0 || strcmp(name, "MARK.VALUE") == 0) {
-                if (!maskSetValues(NULL, NULL, source)) {
+                if (!pmConfigMaskSetInMetadata(NULL, NULL, source)) {
                     psError(PS_ERR_UNKNOWN, false, "Unable to set mask bits.");
                     return 0;
                 }
-                value = psMetadataLookupU8(&mdok, source, name);
+                value = psMetadataLookupImageMaskFromGeneric(&mdok, source, name);
                 psAssert(mdok, "Should have generated mask value");
             } else {
@@ -163,8 +139,67 @@
 }
 
+// lookup an image mask value by name from a psMetadata, without requiring the entry to 
+// be of type psImageMaskType, but verifying that it will fit in psImageMaskType
+psImageMaskType psMetadataLookupImageMaskFromGeneric (bool *status, const psMetadata *md, const char *name) {
+
+    *status = true;
+
+	// select the mask bit name from the header
+	psMetadataItem *item = psMetadataLookup (md, name);
+        if (!item) {
+            psWarning("Unable to find header keyword %s when parsing mask", name);
+	    *status = false;
+	    return 0;
+        }
+
+	// the value may be any of the U8, U16, U32, U64 types : accept the value regardless of type size
+	psU64 fullValue = 0;
+	switch (item->type) {
+	  case PS_DATA_U8:
+	    fullValue = item->data.U8;
+	    break;
+	  case PS_DATA_U16:
+	    fullValue = item->data.U16;
+	    break;
+	  case PS_DATA_U32:
+	    fullValue = item->data.U32;
+	    break;
+	  case PS_DATA_U64:
+	    fullValue = item->data.U64;
+	    break;
+	  case PS_DATA_S8:
+	    fullValue = item->data.S8;
+	    break;
+	  case PS_DATA_S16:
+	    fullValue = item->data.S16;
+	    break;
+	  case PS_DATA_S32:
+	    fullValue = item->data.S32;
+	    break;
+	  case PS_DATA_S64:
+	    fullValue = item->data.S64;
+	    break;
+	  default:
+            psWarning("Mask entry %s in metadata is not of a mask type", name);
+	    *status = false;
+	    return 0;
+	}
+
+	// will the incoming value fit within the current image mask type?
+	if (fullValue > PS_MAX_IMAGE_MASK_TYPE) {
+            psWarning("Mask entry %s in metadata is larger than allowed by the psImageMaskType", name);
+	    *status = false;
+	    return 0;
+        }
+	psImageMaskType value = fullValue;
+        // XXX validate that value is a 2^n value?
+
+	return value;
+}
+
 // Remove from the header keywords starting with the provided string
-static int maskRemoveHeader(psMetadata *header, // Header from which to remove keywords
-                            const char *start // Remove keywords that start with this string
-                            )
+int pmConfigMaskRemoveHeaderKeywords(psMetadata *header, // Header from which to remove keywords
+				     const char *start // Remove keywords that start with this string
+    )
 {
     psString regex = NULL;              // Regular expression for keywords
@@ -182,4 +217,208 @@
 }
 
+// look up the named mask value(s) from the MASKS recipe in the config system
+psImageMaskType pmConfigMaskGet(const char *masks, const pmConfig *config)
+{
+    psAssert(config, "Require configuration");
+    PS_ASSERT_STRING_NON_EMPTY(masks, 0);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *recipe = psMetadataLookupMetadata(&mdok, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return 0;
+    }
+    
+    psImageMaskType mask = pmConfigMaskGetFromMetadata (recipe, masks);
+    return mask;
+}
+
+bool pmConfigMaskSet(const pmConfig *config, const char *maskName, psImageMaskType maskValue)
+{
+    psAssert(config, "Require configuration");
+    PS_ASSERT_STRING_NON_EMPTY(maskName, false);
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return false;
+    }
+
+    bool status = psMetadataAddImageMask(recipe, PS_LIST_TAIL, maskName, PS_META_REPLACE, NULL, maskValue);
+    return status;
+}
+
+
+// replace the named masks in the recipe with values in the header:
+// replace only the names in the header in the recipe
+bool pmConfigMaskReadHeader(pmConfig *config, const psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    bool status = false;
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return false;
+    }
+
+    // MASK.VALUE and MARK.VALUE aren't usually set in the recipe, but may be set in the header: create fake
+    // versions so that it won't complain later
+    if (!psMetadataLookup(recipe, "MASK.VALUE")) {
+        psMetadataAddImageMask(recipe, PS_LIST_TAIL, "MASK.VALUE", 0, "Bits to mask", 0);
+    }
+    if (!psMetadataLookup(recipe, "MARK.VALUE")) {
+        psMetadataAddImageMask(recipe, PS_LIST_TAIL, "MARK.VALUE", 0, "Bits for marking", 0);
+    }
+
+    // How many mask values do we need to read?  We raise an error if this is not found, 
+    // unless the MASK.FORCE is set to true in the camera config
+    int nMask = psMetadataLookupS32(&status, header, "MSKNUM");
+    if (!status) {
+        if (psMetadataLookupBool(&status, config->camera, "MASK.FORCE")) {
+            psWarning("No mask values in header.  Assuming MASKS recipe is accurate because of MASK.FORCE");
+            return true;
+        }
+        psError(PS_ERR_UNKNOWN, true, "Unable to find MSKNUM in header.");
+        return false;
+    }
+
+    // Loop over the expected number of header mask names.  For each named mask value, there
+    // should be a pair of header keywords, one for the name and one for the value
+    char namekey[80];                   // Keyword name for symbolic name of mask entry
+    char valuekey[80];                  // Keyword name for value of mask entry
+    for (int i = 0; i < nMask; i++) {
+        snprintf(namekey,  64, "MSKNAM%02d", i);
+        snprintf(valuekey, 64, "MSKVAL%02d", i);
+
+        char *name = psMetadataLookupStr(&status, header, namekey);
+        if (!status || !name) {
+            psWarning("Unable to find header keyword %s when parsing mask", namekey);
+            continue;
+        }
+
+	psImageMaskType headerValue = psMetadataLookupImageMaskFromGeneric (&status, header, valuekey);
+	if (!status) {
+            psWarning("Failed to get mask value %s from header, skipping", valuekey);
+	    continue;
+	}	    
+
+	// since we may read multiple mask files, we need to warn (or error?) if any of the
+	// header mask values conflict with other header mask values; However, the original
+	// mask values from the recipe do not need to match the header values.
+
+	// when we add a header mask value, we will also add the NAME.ALREADY entry; check for
+	// the NAME.ALREADY entry to see if we have previously added this mask value from a
+	// header.
+
+        psString nameAlready = NULL;    // Name of key with ".ALREADY" added
+        psStringAppend(&nameAlready, "%s.ALREADY", name);
+        bool already = psMetadataLookupBool(&status, recipe, nameAlready); // Already read this one?
+
+	bool inRecipe = false;
+	psImageMaskType recipeValue = psMetadataLookupImageMaskFromGeneric (&inRecipe, recipe, name);
+	if (!inRecipe) {
+            psWarning("Mask value %s is not defined in the recipe", name);
+	}	    
+
+        if (already) {
+	    assert (inRecipe); // XXX makes no sense for NAME.ALREADY to be in without NAME
+            if (recipeValue != headerValue) {
+                psWarning("New mask header value does not match previously loaded entry: %x vs %x", headerValue, recipeValue);
+		psMetadataAddImageMask(recipe, PS_LIST_TAIL, name, PS_META_REPLACE, "Bitmask bit value", headerValue);
+		// XXX alternatively, error here
+            }
+        } else {
+            psMetadataAddBool(recipe, PS_LIST_TAIL, nameAlready, 0, "Already read this mask value", true);
+            psMetadataAddImageMask(recipe, PS_LIST_TAIL, name, PS_META_REPLACE, "Bitmask bit value", headerValue);
+        }
+
+        psFree(nameAlready);
+    }
+
+    return true;
+}
+
+// write the named mask bits to the header
+bool pmConfigMaskWriteHeader(const pmConfig *config, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    pmConfigMaskRemoveHeaderKeywords(header, "MSKNAM");
+    pmConfigMaskRemoveHeaderKeywords(header, "MSKVAL");
+    if (psMetadataLookup(header, "MSKNUM")) {
+        psMetadataRemoveKey(header, "MSKNUM");
+    }
+
+    char namekey[80];
+    char valuekey[80];
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return false;
+    }
+
+    int nMask = 0;
+
+    psMetadataIterator *iter = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+
+	// XXX this would give a false positive for mask which include '.ALREADY' in their names
+	char *ptr = strstr (item->name, ".ALREADY");
+	if (ptr) continue;
+
+	psU64 fullValue = 0;
+        switch (item->type) {
+	  case PS_DATA_U8:
+	    fullValue = item->data.U8;
+	    break;
+	  case PS_DATA_U16:
+	    fullValue = item->data.U16;
+	    break;
+	  case PS_DATA_U32:
+	    fullValue = item->data.U32;
+	    break;
+	  case PS_DATA_U64:
+	    fullValue = item->data.U64;
+	    break;
+	  default:
+            psWarning("mask recipe entry %s is not a bit value\n", item->name);
+            continue;
+        }
+	assert (fullValue <= PS_MAX_IMAGE_MASK_TYPE); // this should have been asserted on read...
+
+        snprintf(namekey,  64, "MSKNAM%02d", nMask);
+        snprintf(valuekey, 64, "MSKVAL%02d", nMask);
+
+        psMetadataAddStr(header, PS_LIST_TAIL, namekey, 0, "Bitmask bit name", item->name);
+        psMetadataAddImageMask(header, PS_LIST_TAIL, valuekey, 0, "Bitmask bit value", fullValue);
+        nMask++;
+    }
+    psFree(iter);
+
+    psMetadataAddS32(header, PS_LIST_TAIL, "MSKNUM", 0, "Bitmask bit count", nMask);
+    return true;
+}
+
+
+bool pmConfigMaskSetBits(psImageMaskType *outMaskValue, psImageMaskType *outMarkValue, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return false;
+    }
+
+    bool status = pmConfigMaskSetInMetadata(outMaskValue, outMarkValue, recipe);
+    return status;
+}
+
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // FPA version of mask functions.  These are not ready to go yet.
@@ -187,12 +426,48 @@
 
 #if 0
-bool pmFPAMaskSetValues(psMaskType *outMaskValue, psMaskType *outMarkValue, pmFPA *fpa)
-{
+
+bool pmFPAMaskWriteHeader(psMetadata *header, const pmFPA *fpa)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
     PS_ASSERT_PTR_NON_NULL(fpa, false);
 
+    // clear out the header of the associated keywords:
+    pmConfigMaskRemoveHeaderKeywords(header, "MSKNAM");
+    pmConfigMaskRemoveHeaderKeywords(header, "MSKVAL");
+    if (psMetadataLookup(header, "MSKNUM")) {
+        psMetadataRemoveKey(header, "MSKNUM");
+    }
+
+    char namekey[80], valuekey[80];     // Mask name and mask value header keywords
+    int numMask = 0;                    // Number of mask entries
+
+    psMetadataIterator *iter = psMetadataIteratorAlloc(fpa->masks, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_TYPE_IMAGE_MASK) {
+            psWarning("mask recipe entry %s is not of a mask type (%x)", item->name, item->type);
+            continue;
+        }
+
+        snprintf(namekey,  64, "MSKNAM%02d", numMask);
+        snprintf(valuekey, 64, "MSKVAL%02d", numMask);
+
+        psMetadataAddStr(header, PS_LIST_TAIL, namekey, 0, "Bitmask bit name", item->name);
+        psMetadataAddImageMask(header, PS_LIST_TAIL, valuekey, 0, "Bitmask bit value", item->data.PS_TYPE_IMAGE_MASK_DATA);
+        numMask++;
+    }
+    psFree(iter);
+
+    return psMetadataAddS32(header, PS_LIST_TAIL, "MSKNUM", 0, "Number of named mask entries", numMask);
+}
+
+bool pmFPAMaskSetValues(psImageMaskType *outMaskValue, psImageMaskType *outMarkValue, pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
     return maskSetValues(outMaskValue, outMarkValue, fpa->masks);
 }
 
-psMaskType pmFPAMaskGet(const pmFPA *fpa, const char *masks, const pmConfig *config)
+psImageMaskType pmFPAMaskGet(const pmFPA *fpa, const char *masks, const pmConfig *config)
 {
     PS_ASSERT_PTR_NON_NULL(fpa, 0);
@@ -201,10 +476,10 @@
 
     if (fpa->masks) {
-        return maskGet(fpa->masks, masks);
+        return pmConfigMaskGetFromMetadata(fpa->masks, masks);
     }
     return pmConfigMaskGet(masks, config);
 }
 
-bool pmFPAMaskSet(pmFPA *fpa, const char *maskName, psMaskType maskValue)
+bool pmFPAMaskSet(pmFPA *fpa, const char *maskName, psImageMaskType maskValue)
 {
     PS_ASSERT_PTR_NON_NULL(fpa, 0);
@@ -214,5 +489,5 @@
         fpa->masks = psMetadataAlloc();
     }
-    return psMetadataAddU8(fpa->masks, PS_LIST_TAIL, maskName, PS_META_REPLACE, NULL, maskValue);
+    return psMetadataAddImageMask(fpa->masks, PS_LIST_TAIL, maskName, PS_META_REPLACE, NULL, maskValue);
 }
 
@@ -250,5 +525,5 @@
             continue;
         }
-        psU8 bit = psMetadataLookupU8(&mdok, header, valuekey);
+        psImageMaskType bit = psMetadataLookupImageMask(&mdok, header, valuekey);
         if (!mdok) {
             psWarning("Unable to find header keyword %s when parsing mask", namekey);
@@ -260,12 +535,12 @@
         psMetadataItem *item = psMetadataLookup(fpa->masks, name); // Item in recipe with current value
         if (item) {
-            psAssert(item->type == PS_TYPE_MASK, "Mask entry %s is not of a mask type (%x)",
+            psAssert(item->type == PS_TYPE_IMAGE_MASK, "Mask entry %s is not of a mask type (%x)",
                      name, item->type);
-            if (item->data.PS_TYPE_MASK_DATA != bit) {
+            if (item->data.PS_TYPE_IMAGE_MASK_DATA != bit) {
                 psWarning("New mask entry %s doesn't match previously loaded entry: %x vs %x",
-                          name, bit, item->data.PS_TYPE_MASK_DATA);
+                          name, bit, item->data.PS_TYPE_IMAGE_MASK_DATA);
             }
         } else {
-            psMetadataAddU8(fpa->masks, PS_LIST_TAIL, name, 0, NULL, bit);
+            psMetadataAddImageMask(fpa->masks, PS_LIST_TAIL, name, 0, NULL, bit);
         }
     }
@@ -281,11 +556,11 @@
     psMetadataItem *item;               // Item from iteration
     while ((item = psMetadataGetAndIncrement(iter))) {
-        if (item->type != PS_TYPE_MASK) {
+        if (item->type != PS_TYPE_IMAGE_MASK) {
             psWarning("Recipe mask entry %s is not of a mask type (%x)", item->name, item->type);
             continue;
         }
         if (!psMetadataLookup(fpa->masks, item->name)) {
-            psMetadataAddU8(fpa->masks, PS_LIST_TAIL, item->name, 0, item->comment,
-                            item->data.PS_TYPE_MASK_DATA);
+            psMetadataAddImageMask(fpa->masks, PS_LIST_TAIL, item->name, 0, item->comment,
+                            item->data.PS_TYPE_IMAGE_MASK_DATA);
         }
     }
@@ -294,220 +569,3 @@
     return true;
 }
-
-
-bool pmFPAMaskWriteHeader(psMetadata *header, const pmFPA *fpa)
-{
-    PS_ASSERT_METADATA_NON_NULL(header, false);
-    PS_ASSERT_PTR_NON_NULL(fpa, false);
-
-    maskRemoveHeader(header, "MSKNAM");
-    maskRemoveHeader(header, "MSKVAL");
-    if (psMetadataLookup(header, "MSKNUM")) {
-        psMetadataRemoveKey(header, "MSKNUM");
-    }
-
-    char namekey[80], valuekey[80];     // Mask name and mask value header keywords
-    int numMask = 0;                    // Number of mask entries
-
-    psMetadataIterator *iter = psMetadataIteratorAlloc(fpa->masks, PS_LIST_HEAD, NULL); // Iterator
-    psMetadataItem *item;               // Item from iteration
-    while ((item = psMetadataGetAndIncrement(iter))) {
-        if (item->type != PS_TYPE_MASK) {
-            psWarning("mask recipe entry %s is not of a mask type (%x)", item->name, item->type);
-            continue;
-        }
-
-        snprintf(namekey,  64, "MSKNAM%02d", numMask);
-        snprintf(valuekey, 64, "MSKVAL%02d", numMask);
-
-        psMetadataAddStr(header, PS_LIST_TAIL, namekey, 0, "Bitmask bit name", item->name);
-        psMetadataAddU8(header, PS_LIST_TAIL, valuekey, 0, "Bitmask bit value", item->data.PS_TYPE_MASK_DATA);
-        numMask++;
-    }
-    psFree(iter);
-
-    return psMetadataAddS32(header, PS_LIST_TAIL, "MSKNUM", 0, "Number of named mask entries", numMask);
-}
-
 #endif
-
-
-psMaskType pmConfigMaskGet(const char *masks, const pmConfig *config)
-{
-    psAssert(config, "Require configuration");
-    PS_ASSERT_STRING_NON_EMPTY(masks, 0);
-
-    bool mdok;                          // Status of MD lookup
-    psMetadata *recipe = psMetadataLookupMetadata(&mdok, config->recipes, "MASKS"); // The recipe
-    if (!recipe) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
-        return 0;
-    }
-    return maskGet(recipe, masks);
-}
-
-
-bool pmConfigMaskSet(const pmConfig *config, const char *maskName, psMaskType maskValue)
-{
-    psAssert(config, "Require configuration");
-    PS_ASSERT_STRING_NON_EMPTY(maskName, false);
-
-    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
-    if (!recipe) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
-        return false;
-    }
-
-    return psMetadataAddU8(recipe, PS_LIST_TAIL, maskName, PS_META_REPLACE, NULL, maskValue);
-}
-
-
-// replace the named masks in the recipe with values in the header:
-// replace only the names in the header in the recipe
-bool pmConfigMaskReadHeader(pmConfig *config, const psMetadata *header)
-{
-    PS_ASSERT_PTR_NON_NULL(config, false);
-    PS_ASSERT_METADATA_NON_NULL(header, false);
-
-    bool status = false;
-
-    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
-    if (!recipe) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
-        return false;
-    }
-
-    // MASK.VALUE and MARK.VALUE aren't usually set in the recipe, but may be set in the header: create fake
-    // versions so that it won't complain later
-    if (!psMetadataLookup(recipe, "MASK.VALUE")) {
-        psMetadataAddU8(recipe, PS_LIST_TAIL, "MASK.VALUE", 0, "Bits to mask", 0);
-    }
-    if (!psMetadataLookup(recipe, "MARK.VALUE")) {
-        psMetadataAddU8(recipe, PS_LIST_TAIL, "MARK.VALUE", 0, "Bits for marking", 0);
-    }
-
-    int nMask = psMetadataLookupS32(&status, header, "MSKNUM");
-    if (!status) {
-        if (psMetadataLookupBool(&status, config->camera, "MASK.FORCE")) {
-            psWarning("No mask values in header.  Assuming MASKS recipe is accurate because of MASK.FORCE");
-            return true;
-        }
-        psError(PS_ERR_UNKNOWN, true, "Unable to find MSKNUM in header.");
-        return false;
-    }
-
-    char namekey[80];                   // Keyword name for symbolic name of mask entry
-    char valuekey[80];                  // Keyword name for value of mask entry
-    for (int i = 0; i < nMask; i++) {
-        snprintf(namekey,  64, "MSKNAM%02d", i);
-        snprintf(valuekey, 64, "MSKVAL%02d", i);
-
-        char *name = psMetadataLookupStr(&status, header, namekey);
-        if (!status || !name) {
-            psWarning("Unable to find header keyword %s when parsing mask", namekey);
-            continue;
-        }
-        psU8 bit = psMetadataLookupU8(&status, header, valuekey);
-        if (!status) {
-            psWarning("Unable to find header keyword %s when parsing mask", namekey);
-            continue;
-        }
-
-        // XXX validate that bit is a 2^n value?
-
-        psString nameAlready = NULL;    // Name of key with ".ALREADY" added
-        psStringAppend(&nameAlready, "%s.ALREADY", name);
-        bool already = psMetadataLookupBool(&status, recipe, nameAlready); // Already read this one?
-
-        psMetadataItem *item = psMetadataLookup(recipe, name); // Item in recipe with current value
-        if (item && item->type != PS_TYPE_MASK) {
-            psWarning("Mask recipe entry is not of a mask type (%x)", item->type);
-            item->type = PS_TYPE_MASK;
-        }
-
-        if (already) {
-            if (item && item->data.U8 != bit) {
-                psWarning("New mask recipe entry doesn't match previously loaded entry: %x vs %x",
-                          bit, item->data.U8);
-            }
-        } else {
-            psMetadataAddBool(recipe, PS_LIST_TAIL, nameAlready, 0, "Already read this mask value", true);
-        }
-
-        if (!item) {
-            psWarning("Mask recipe entry %s not in recipe\n", name);
-            psMetadataAddU8(recipe, PS_LIST_TAIL, name, 0, "Bitmask bit value", bit);
-        } else {
-            item->data.U8 = bit;
-        }
-
-        psFree(nameAlready);
-    }
-
-
-    return true;
-}
-
-
-
-// write the named mask bits to the header
-bool pmConfigMaskWriteHeader(const pmConfig *config, psMetadata *header)
-{
-    PS_ASSERT_PTR_NON_NULL(config, false);
-    PS_ASSERT_METADATA_NON_NULL(header, false);
-
-    maskRemoveHeader(header, "MSKNAM");
-    maskRemoveHeader(header, "MSKVAL");
-    if (psMetadataLookup(header, "MSKNUM")) {
-        psMetadataRemoveKey(header, "MSKNUM");
-    }
-
-    char namekey[80];
-    char valuekey[80];
-
-    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
-    if (!recipe) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
-        return false;
-    }
-
-    int nMask = 0;
-
-    psMetadataIterator *iter = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, NULL); // Iterator
-    psMetadataItem *item;               // Item from iteration
-    while ((item = psMetadataGetAndIncrement(iter))) {
-        if (strcmp(item->name + strlen(item->name) - strlen(".ALREADY"), ".ALREADY") == 0) {
-            continue;
-        }
-
-        if (item->type != PS_DATA_U8) {
-            psWarning("mask recipe entry %s is not a bit value\n", item->name);
-            continue;
-        }
-
-        snprintf(namekey,  64, "MSKNAM%02d", nMask);
-        snprintf(valuekey, 64, "MSKVAL%02d", nMask);
-
-        psMetadataAddStr(header, PS_LIST_TAIL, namekey, 0, "Bitmask bit name", item->name);
-        psMetadataAddU8(header, PS_LIST_TAIL, valuekey, 0, "Bitmask bit value", item->data.U8);
-        nMask++;
-    }
-    psFree(iter);
-
-    psMetadataAddS32(header, PS_LIST_TAIL, "MSKNUM", 0, "Bitmask bit count", nMask);
-    return true;
-}
-
-
-bool pmConfigMaskSetBits(psMaskType *outMaskValue, psMaskType *outMarkValue, const pmConfig *config)
-{
-    PS_ASSERT_PTR_NON_NULL(config, false);
-
-    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
-    if (!recipe) {
-        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
-        return false;
-    }
-
-    return maskSetValues(outMaskValue, outMarkValue, recipe);
-}
