Index: trunk/psModules/src/camera/Makefile.am
===================================================================
--- trunk/psModules/src/camera/Makefile.am	(revision 13791)
+++ trunk/psModules/src/camera/Makefile.am	(revision 13810)
@@ -5,4 +5,5 @@
 libpsmodulescamera_la_SOURCES  = \
 	pmFPA.c \
+	pmFPACalibration.c \
 	pmFPAConstruct.c \
 	pmFPACopy.c \
@@ -29,4 +30,5 @@
 pkginclude_HEADERS = \
 	pmFPA.h \
+	pmFPACalibration.h \
 	pmFPAConstruct.h \
 	pmFPACopy.h \
Index: trunk/psModules/src/camera/pmFPACalibration.c
===================================================================
--- trunk/psModules/src/camera/pmFPACalibration.c	(revision 13810)
+++ trunk/psModules/src/camera/pmFPACalibration.c	(revision 13810)
@@ -0,0 +1,63 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmFPACalibration.h"
+
+float pmFPADarkNorm(const pmFPA *fpa, const pmFPAview *view, float expTime)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NAN);
+    PS_ASSERT_PTR_NON_NULL(view, NAN);
+
+    psMetadata *darkNorms = psMetadataLookupMetadata(NULL, fpa->camera, "DARK.NORM"); // Dark normalisations
+    if (!darkNorms) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find DARK.NORM in camera configuration.");
+        return NAN;
+    }
+
+    const char *key = psMetadataLookupStr(NULL, fpa->camera, "DARK.NORM.KEY"); // Key for normalisation
+    if (!key || strlen(key) == 0) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find DARK.NORM.KEY in camera configuration.");
+        return NAN;
+    }
+
+    psString keyResolved = pmFPANameFromRule(key, fpa, view); // Resolved version
+    if (!keyResolved || strlen(keyResolved) == 0) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to resolve DARK.NORM.KEY: %s", key);
+        return NAN;
+    }
+
+    psMetadata *polyMD = psMetadataLookupMetadata(NULL, darkNorms, keyResolved); // Metadata with polynomial
+    if (!polyMD) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find %s polynomial in the DARK.NORM metadata", keyResolved);
+        psFree(keyResolved);
+        return NAN;
+    }
+
+    psPolynomial1D *poly = psPolynomial1DfromMetadata(polyMD); // Polynomial
+    if (!poly) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine polynomial from %s in the DARK.NORM metadata",
+                keyResolved);
+        psFree(keyResolved);
+        return NAN;
+    }
+    psFree(keyResolved);
+
+    float value = psPolynomial1DEval(poly, expTime);
+
+    psFree(poly);
+
+    return value;
+}
Index: trunk/psModules/src/camera/pmFPACalibration.h
===================================================================
--- trunk/psModules/src/camera/pmFPACalibration.h	(revision 13810)
+++ trunk/psModules/src/camera/pmFPACalibration.h	(revision 13810)
@@ -0,0 +1,17 @@
+#ifndef PM_FPA_CALIBRATION_H
+#define PM_FPA_CALIBRATION_H
+
+/// Return the dark normalisation value
+///
+/// Unfortunately, dark current is not linear with the exposure time, but application of a polynomial
+/// correction to the exposure time should make it linear.  This function returns the appropriate value with
+/// which to normalise a dark frame.  The polynomial is obtained from DARK.NORM in the camera configuration.
+/// The specific polynomial metadata to use is provided by DARK.NORM.KEY, which is keyword expanded in the
+/// usual manner (e.g., try "{CHIP.NAME}").
+float pmFPADarkNorm(const pmFPA *fpa,   ///< FPA for which to get the normalisation
+                    const pmFPAview *view, ///< View to the FPA component of interest
+                    float expTime       ///< The nominal exposure time
+    );
+
+
+#endif
Index: trunk/psModules/src/camera/pmFPAfile.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.c	(revision 13791)
+++ trunk/psModules/src/camera/pmFPAfile.c	(revision 13810)
@@ -5,5 +5,5 @@
 #include <stdio.h>
 #include <string.h>
-#include <strings.h>		/* for strn?casecmp */
+#include <strings.h>            /* for strn?casecmp */
 #include <pslib.h>
 
@@ -164,24 +164,14 @@
 }
 
-// select the rule from the camera configuration, perform substitutions as needed
-psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view)
-{
-    PS_ASSERT_PTR_NON_NULL(rule, NULL);
-    PS_ASSERT_INT_POSITIVE(strlen(rule), NULL);
-    PS_ASSERT_PTR_NON_NULL(file, NULL);
+psString pmFPANameFromRule(const char *rule, const pmFPA *fpa, const pmFPAview *view)
+{
+    PS_ASSERT_STRING_NON_EMPTY(rule, NULL);
     PS_ASSERT_PTR_NON_NULL(view, NULL);
 
-    char *newName = NULL;     // destination for resulting name
-
-    newName = psStringCopy (rule);
-
-    if (strstr (newName, "{OUTPUT}") != NULL) {
-        char *name = psMetadataLookupStr (NULL, file->names, "OUTPUT");
-        if (name != NULL) {
-            psStringSubstitute(&newName, name, "{OUTPUT}");
-        }
-    }
+    psString newName = NULL;            // New name, to be returned
+    newName = psStringCopy(rule);
+
     if (strstr (newName, "{CHIP.NAME}") != NULL) {
-        pmChip *chip = pmFPAviewThisChip (view, file->fpa);
+        pmChip *chip = pmFPAviewThisChip (view, fpa);
         if (chip != NULL) {
             char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
@@ -202,5 +192,5 @@
     }
     if (strstr (newName, "{CELL.NAME}") != NULL) {
-        pmCell *cell = pmFPAviewThisCell (view, file->fpa);
+        pmCell *cell = pmFPAviewThisCell (view, fpa);
         if (cell != NULL) {
             char *name = psMetadataLookupStr (NULL, cell->concepts, "CELL.NAME");
@@ -220,5 +210,5 @@
     }
     if (strstr (newName, "{EXTNAME}") != NULL) {
-        pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
+        pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
         if (hdu->extname && *hdu->extname) {
             psStringSubstitute(&newName, hdu->extname, "{EXTNAME}");
@@ -226,6 +216,6 @@
     }
     if (strstr (newName, "{FILTER}") != NULL) {
-        if (file->fpa != NULL) {
-            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.FILTER");
+        if (fpa != NULL) {
+            char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTER");
             if (name && *name) {
                 psStringSubstitute(&newName, name, "{FILTER}");
@@ -234,6 +224,6 @@
     }
     if (strstr (newName, "{FILTER.ID}") != NULL) {
-        if (file->fpa != NULL) {
-            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.FILTERID");
+        if (fpa != NULL) {
+            char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTERID");
             if (name && *name) {
                 psStringSubstitute(&newName, name, "{FILTER.ID}");
@@ -242,6 +232,6 @@
     }
     if (strstr (newName, "{CAMERA}") != NULL) {
-        if (file->fpa != NULL) {
-            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.INSTRUMENT");
+        if (fpa != NULL) {
+            char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
             if (name && *name) {
                 psStringSubstitute(&newName, name, "{CAMERA}");
@@ -250,6 +240,6 @@
     }
     if (strstr (newName, "{INSTRUMENT}") != NULL) {
-        if (file->fpa != NULL) {
-            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.INSTRUMENT");
+        if (fpa != NULL) {
+            char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
             if (name && *name) {
                 psStringSubstitute(&newName, name, "{INSTRUMENT}");
@@ -258,6 +248,6 @@
     }
     if (strstr (newName, "{DETECTOR}") != NULL) {
-        if (file->fpa != NULL) {
-            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.DETECTOR");
+        if (fpa != NULL) {
+            char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.DETECTOR");
             if (name && *name) {
                 psStringSubstitute(&newName, name, "{DETECTOR}");
@@ -266,6 +256,6 @@
     }
     if (strstr (newName, "{TELESCOPE}") != NULL) {
-        if (file->fpa != NULL) {
-            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.TELESCOPE");
+        if (fpa != NULL) {
+            char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.TELESCOPE");
             if (name && *name) {
                 psStringSubstitute(&newName, name, "{TELESCOPE}");
@@ -273,4 +263,28 @@
         }
     }
+    return newName;
+}
+
+// select the rule from the camera configuration, perform substitutions as needed
+psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(rule, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(rule), NULL);
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+    psString newRule = NULL;            // Rule to pass on to pmFPANameFromRule
+    newRule = psStringCopy(rule);
+
+    if (strstr(newRule, "{OUTPUT}") != NULL) {
+        char *name = psMetadataLookupStr(NULL, file->names, "OUTPUT");
+        if (name) {
+            psStringSubstitute(&newRule, name, "{OUTPUT}");
+        }
+    }
+
+    psString newName = pmFPANameFromRule(newRule, file->fpa, view); // New name, to be returned
+    psFree(newRule);
+
     return newName;
 }
@@ -346,5 +360,5 @@
         status = pmChipCopyStructure (outChip, inChip, xBin, yBin);
         return status;
-    } 
+    }
     if (view->cell >= inChip->cells->n) {
         psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
@@ -410,31 +424,31 @@
     switch (type) {
       case PM_FPA_FILE_SX:
-	return ("SX");
+        return ("SX");
       case PM_FPA_FILE_OBJ:
-	return ("OBJ");
+        return ("OBJ");
       case PM_FPA_FILE_CMP:
-	return ("CMP");
+        return ("CMP");
       case PM_FPA_FILE_CMF:
-	return ("CMF");
+        return ("CMF");
       case PM_FPA_FILE_RAW:
-	return ("RAW");
+        return ("RAW");
       case PM_FPA_FILE_IMAGE:
-	return ("IMAGE");
+        return ("IMAGE");
       case PM_FPA_FILE_PSF:
-	return ("PSF");
+        return ("PSF");
       case PM_FPA_FILE_JPEG:
-	return ("JPEG");
+        return ("JPEG");
       case PM_FPA_FILE_KAPA:
-	return ("KAPA");
+        return ("KAPA");
       case PM_FPA_FILE_MASK:
-	return ("MASK");
+        return ("MASK");
       case PM_FPA_FILE_WEIGHT:
-	return ("WEIGHT");
+        return ("WEIGHT");
       case PM_FPA_FILE_FRINGE:
-	return ("FRINGE");
+        return ("FRINGE");
       case PM_FPA_FILE_HEADER:
-	return ("HEADER");
+        return ("HEADER");
       default:
-	return ("NONE");
+        return ("NONE");
     }
     return ("NONE");
Index: trunk/psModules/src/camera/pmFPAfile.h
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.h	(revision 13791)
+++ trunk/psModules/src/camera/pmFPAfile.h	(revision 13810)
@@ -4,6 +4,6 @@
  * @author EAM, IfA
  *
- * @version $Revision: 1.17 $ $Name: not supported by cvs2svn $
- * @date $Date: 2007-05-26 02:50:36 $
+ * @version $Revision: 1.18 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-14 02:03:29 $
  * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
  */
@@ -53,29 +53,29 @@
 typedef struct
 {
-    pmFPAfileMode mode;			// is this file read, written, or only used internally?
-    pmFPAfileType type;			// what type of data is read from / written to disk?
-    pmFPAfileState state;		// have we opened the file, etc?
+    pmFPAfileMode mode;                 // is this file read, written, or only used internally?
+    pmFPAfileType type;                 // what type of data is read from / written to disk?
+    pmFPAfileState state;               // have we opened the file, etc?
 
-    pmFPALevel fileLevel;	        // what level in the FPA hierarchy represents a unique file?
-    pmFPALevel dataLevel;		// at what level do we read/write the data segment? (request by user)
-    pmFPALevel freeLevel;		// at what level do we free the data segment? (set by program)
+    pmFPALevel fileLevel;               // what level in the FPA hierarchy represents a unique file?
+    pmFPALevel dataLevel;               // at what level do we read/write the data segment? (request by user)
+    pmFPALevel freeLevel;               // at what level do we free the data segment? (set by program)
     pmFPALevel mosaicLevel;             // at what level is the mosaic?
 
-    pmFPA *fpa;			        // for I/O files, we carry a pointer to the complete fpa
-    psFits *fits;	                // for I/O files of fits type (IMAGE, CMP, CMF), we carry a file handle
+    pmFPA *fpa;                         // for I/O files, we carry a pointer to the complete fpa
+    psFits *fits;                       // for I/O files of fits type (IMAGE, CMP, CMF), we carry a file handle
 
-    bool phu;				// have we written a PHU for this file?
-    psMetadata *header;			// pointer (view) to the current hdu header
+    bool phu;                           // have we written a PHU for this file?
+    psMetadata *header;                 // pointer (view) to the current hdu header
 
-    pmReadout *readout;			// for internal files, we only carry a single readout
+    pmReadout *readout;                 // for internal files, we only carry a single readout
 
-    psMetadata *names;		        // filenames supplied by the cmdline or detdb are saved here
+    psMetadata *names;                  // filenames supplied by the cmdline or detdb are saved here
 
-    char *filerule;			// rule for constructing a filename when needed
-    char *filesrc;	                // rule to find file in pmFPAfile->names list
+    char *filerule;                     // rule for constructing a filename when needed
+    char *filesrc;                      // rule to find file in pmFPAfile->names list
 
-    char *name;        			// the name of the rule (useful for debugging / tracing)
-    char *filename;    			// the current name of an active file
-    char *extname;     			// the current name of an active file extension
+    char *name;                         // the name of the rule (useful for debugging / tracing)
+    char *filename;                     // the current name of an active file
+    char *extname;                      // the current name of an active file extension
 
     pmDetrendSelectResults *detrend;    // Detrend information, from pmDetrendSelect
@@ -110,4 +110,7 @@
 
 // convert the rule to a name based on the current view
+psString pmFPANameFromRule(const char *rule, const pmFPA *fpa, const pmFPAview *view);
+
+// convert the rule to a name based on the current view
 psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view);
 
