Index: trunk/psModules/src/camera/Makefile.am
===================================================================
--- trunk/psModules/src/camera/Makefile.am	(revision 13803)
+++ 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 13803)
+++ 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 13803)
+++ 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);
 
Index: trunk/psModules/src/config/pmConfig.c
===================================================================
--- trunk/psModules/src/config/pmConfig.c	(revision 13803)
+++ trunk/psModules/src/config/pmConfig.c	(revision 13810)
@@ -4,6 +4,6 @@
  *  @author EAM (IfA)
  *
- *  @version $Revision: 1.95 $ $Name: not supported by cvs2svn $
- *  @date $Date: 2007-06-08 00:31:50 $
+ *  @version $Revision: 1.96 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-06-14 02:03:29 $
  *
  *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
@@ -293,4 +293,49 @@
 }
 
+// Read the calibrations for a camera
+static bool cameraReadCalibrations(psMetadata *camera, // Camera for which to read the formats
+                                   const char *name // Name of the camera, for error messages
+    )
+{
+    assert(camera);
+    assert(name);
+
+    psMetadataItem *darkNorm = psMetadataLookup(camera, "DARK.NORM"); // The dark normalisation calibration
+    if (darkNorm) {
+        if (darkNorm->type == PS_DATA_STRING) {
+            const char *darkNormName = darkNorm->data.str; // The file name
+            psTrace("config", 2, "Reading %s dark normalisation: %s\n", name, darkNormName);
+            psMetadata *new = NULL;         // New metadata
+            if (!pmConfigFileRead(&new, darkNormName, "Dark normalisation")) {
+                psError(PM_ERR_CONFIG, false, "Trouble reading reading %s dark normalisation %s --- "
+                        "ignored.\n", name, darkNormName);
+                psFree(new);
+                return false;
+            }
+
+            // Muck around under the hood to replace the filename with the metadata;
+            // don't try this at home, kids
+            darkNorm->type = PS_DATA_METADATA;
+            psFree(darkNorm->data.str);
+            darkNorm->data.md = new;
+        } else if (darkNorm->type != PS_DATA_METADATA) {
+            psWarning("DARK.NORM in camera %s is not of type STR or METADATA (%x)", name, darkNorm->type);
+        }
+    } else {
+        // Add a dummy entry
+        psPolynomial1D *poly = psPolynomial1DAlloc(1, PS_POLYNOMIAL_ORD); // Dummy polynomial
+        poly->coeff[0] = 0.0;
+        poly->coeff[1] = 1.0;
+        psMetadata *polyMD = psMetadataAlloc(); // Container for the polynomial
+        (void)psPolynomial1DtoMetadata(polyMD, poly, "_DEFAULT"); // Metadata to insert
+        psFree(poly);
+        psMetadataAddMetadata(camera, PS_LIST_TAIL, "DARK.NORM", 0, "Dark normalisation polynomial",
+                              polyMD);
+        psMetadataAddStr(camera, PS_LIST_TAIL, "DARK.NORM.KEY", 0, "Key for dark normalisation", "_DEFAULT");
+        psFree(polyMD);
+    }
+
+    return true;
+}
 
 pmConfig *pmConfigRead(int *argc, char **argv, const char *defaultRecipe)
@@ -530,4 +575,13 @@
             }
 
+            // Read in any camera-specific calibrations
+            if (!cameraReadCalibrations(config->camera, cameraFile)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to read calibrations within camera configuration %s.\n",
+                        cameraFile);
+                psFree(config);
+                return NULL;
+            }
+
             psMetadataAddMetadata(cameras, PS_LIST_HEAD, cameraFile, PS_META_REPLACE,
                                   "Camera specified on command line", config->camera);
Index: trunk/psModules/src/detrend/pmBias.c
===================================================================
--- trunk/psModules/src/detrend/pmBias.c	(revision 13803)
+++ trunk/psModules/src/detrend/pmBias.c	(revision 13810)
@@ -11,4 +11,8 @@
 #include "pmFPA.h"
 #include "pmHDUUtils.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPACalibration.h"
+
 #include "pmBias.h"
 
@@ -206,25 +210,25 @@
 }
 
-
-bool pmBiasSubtract(pmReadout *in, pmOverscanOptions *overscanOpts,
-                    const pmReadout *bias, const pmReadout *dark)
+bool pmBiasSubtract(pmReadout *inRO, pmOverscanOptions *overscanOpts,
+                    const pmReadout *biasRO, const pmReadout *darkRO, const pmFPAview *view)
 {
     psTrace("psModules.detrend", 4,
             "---- pmBiasSubtract() begin ----\n");
-    PS_ASSERT_PTR_NON_NULL(in, NULL);
-    PS_ASSERT_IMAGE_NON_NULL(in->image, NULL);
-    PS_ASSERT_IMAGE_TYPE(in->image, PS_TYPE_F32, NULL);
-    if (bias) {
-        PS_ASSERT_IMAGE_NON_NULL(bias->image, NULL);
-        PS_ASSERT_IMAGE_TYPE(bias->image, PS_TYPE_F32, NULL);
-    }
-    if (dark) {
-        PS_ASSERT_IMAGE_NON_NULL(dark->image, NULL);
-        PS_ASSERT_IMAGE_TYPE(dark->image, PS_TYPE_F32, NULL);
-    }
-
-    pmHDU *hdu = pmHDUFromReadout(in);  // HDU of interest
-
-    psImage *image = in->image;         // The input image
+
+    PS_ASSERT_PTR_NON_NULL(inRO, false);
+    PS_ASSERT_IMAGE_NON_NULL(inRO->image, false);
+    PS_ASSERT_IMAGE_TYPE(inRO->image, PS_TYPE_F32, false);
+    if (biasRO) {
+        PS_ASSERT_IMAGE_NON_NULL(biasRO->image, false);
+        PS_ASSERT_IMAGE_TYPE(biasRO->image, PS_TYPE_F32, false);
+    }
+    if (darkRO) {
+        PS_ASSERT_PTR_NON_NULL(view, false);
+        PS_ASSERT_IMAGE_NON_NULL(darkRO->image, false);
+        PS_ASSERT_IMAGE_TYPE(darkRO->image, PS_TYPE_F32, false);
+    }
+
+    pmHDU *hdu = pmHDUFromReadout(inRO);  // HDU of interest
+    psImage *image = inRO->image;         // The input image
 
     // Overscan processing
@@ -238,5 +242,5 @@
         }
 
-        psList *overscans = in->bias; // List of the overscan images
+        psList *overscans = inRO->bias; // List of the overscan images
 
         psStatsOptions statistic = psStatsSingleOption(overscanOpts->stat->options); // Statistic to use
@@ -281,6 +285,7 @@
             psFree(comment);
 
-	    // write metadata header value
-            psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan value", reduced);
+            // write metadata header value
+            psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan value",
+                             reduced);
             psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", NAN);
 
@@ -288,5 +293,6 @@
         } else {
             // We do the regular overscan subtraction
-            bool readRows = psMetadataLookupBool(NULL, in->parent->concepts, "CELL.READDIR");// Read direction
+            bool readRows = psMetadataLookupBool(NULL, inRO->parent->concepts,
+                                                 "CELL.READDIR"); // Read direction
             float chi2 = NAN;           // chi^2 from fit
 
@@ -302,11 +308,11 @@
                 psImage *overscan = NULL; // Overscan image from iterator
                 while ((overscan = psListGetAndIncrement(iter))) {
-		    // the overscan and image might not be aligned.  pixels->data represents
-		    // the image row pixels.
+                    // the overscan and image might not be aligned.  pixels->data represents
+                    // the image row pixels.
                     int diff = overscan->row0 - image->row0; // Offset between the two regions
                     for (int i = PS_MAX(0,diff); i < PS_MIN(image->numRows, overscan->numRows + diff); i++) {
-			int j = i - diff;
+                        int j = i - diff;
                         // i is row on image
-			// j is row on overscan
+                        // j is row on overscan
                         psVector *values = pixels->data[i];
                         int index = values->n; // Index in the vector
@@ -349,16 +355,16 @@
                 psImage *overscan = NULL; // Overscan image from iterator
                 while ((overscan = psListGetAndIncrement(iter))) {
-		    // the overscan and image might not be aligned.  pixels->data represents
-		    // the image row pixels.
+                    // the overscan and image might not be aligned.  pixels->data represents
+                    // the image row pixels.
                     int diff = overscan->col0 - image->col0; // Offset between the two regions
                     for (int i = PS_MAX(0,diff); i < PS_MIN(image->numCols, overscan->numCols + diff); i++) {
-			int I = i - diff;
+                        int iFixed = i - diff;
                         // i is column on image
-                        // I is column on overscan
+                        // iFixed is column on overscan
                         psVector *values = pixels->data[i];
                         int index = values->n; // Index in the vector
                         values = psVectorRealloc(values, values->n + overscan->numRows);
-                        for (int J = 0; J < overscan->numRows; J++) {
-                            values->data.F32[index++] = overscan->data.F32[J][I];
+                        for (int j = 0; j < overscan->numRows; j++) {
+                            values->data.F32[index++] = overscan->data.F32[j][iFixed];
                         }
                         values->n += overscan->numRows;
@@ -397,7 +403,9 @@
                     psFree(comment);
 
-		    // write metadata header value
-		    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan value", poly->coeff[0]);
-		    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", poly->coeffErr[0]);
+                    // write metadata header value
+                    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE,
+                                     "Overscan value", poly->coeff[0]);
+                    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE,
+                                     "Overscan stdev", poly->coeffErr[0]);
                     break;
                 }
@@ -414,7 +422,9 @@
                         psFree(comment);
                     }
-		    // write metadata header value
-		    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan value", NAN);
-		    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", NAN);
+                    // write metadata header value
+                    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE,
+                                     "Overscan value", NAN);
+                    psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE,
+                                     "Overscan stdev", NAN);
                     break;
                 }
@@ -431,6 +441,6 @@
 
     // Bias frame subtraction
-    if (bias) {
-        psVector *md5 = psImageMD5(bias->image); // md5 hash
+    if (biasRO) {
+        psVector *md5 = psImageMD5(biasRO->image); // md5 hash
         psString md5string = psMD5toString(md5); // String
         psFree(md5);
@@ -440,23 +450,36 @@
         psFree(md5string);
 
-        if (!subtractFrame(in, bias, 1.0)) {
+        if (!subtractFrame(inRO, biasRO, 1.0)) {
             return false;
         }
     }
 
-    if (dark) {
+    if (darkRO) {
         // Get the scaling
-        float inTime = psMetadataLookupF32(NULL, in->parent->concepts, "CELL.DARKTIME");
-        float darkTime = psMetadataLookupF32(NULL, dark->parent->concepts, "CELL.DARKTIME");
-
-        psVector *md5 = psImageMD5(dark->image); // md5 hash
+        float inTime = psMetadataLookupF32(NULL, inRO->parent->concepts, "CELL.DARKTIME");
+        float darkTime = psMetadataLookupF32(NULL, darkRO->parent->concepts, "CELL.DARKTIME");
+        if (isnan(inTime) || isnan(darkTime)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine dark scaling.");
+            return false;
+        }
+
+        float inNorm = pmFPADarkNorm(inRO->parent->parent->parent, view, inTime);
+        float darkNorm = pmFPADarkNorm(darkRO->parent->parent->parent, view, darkTime);
+        if (isnan(inNorm) || isnan(darkNorm)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine dark normalisations.");
+            return false;
+        }
+
+        float scale = inNorm / darkNorm;// Scaling to apply to dark exposure
+
+        psVector *md5 = psImageMD5(darkRO->image); // md5 hash
         psString md5string = psMD5toString(md5); // String
         psFree(md5);
-        psStringPrepend(&md5string, "DARK image (scale %.3f) MD5: ", inTime/darkTime);
+        psStringPrepend(&md5string, "DARK image (scale %.3f) MD5: ", scale);
         psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
                          md5string, "");
         psFree(md5string);
 
-        if (!subtractFrame(in, dark, inTime/darkTime)) {
+        if (!subtractFrame(inRO, darkRO, scale)) {
             return false;
         }
Index: trunk/psModules/src/detrend/pmBias.h
===================================================================
--- trunk/psModules/src/detrend/pmBias.h	(revision 13803)
+++ trunk/psModules/src/detrend/pmBias.h	(revision 13810)
@@ -5,6 +5,6 @@
  * @author Paul Price, IfA
  *
- * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
- * @date $Date: 2007-03-30 21:12:56 $
+ * @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-14 02:03:29 $
  * Copyright 2004--2006 Institute for Astronomy, University of Hawaii
  */
@@ -57,6 +57,7 @@
 bool pmBiasSubtract(pmReadout *in,      ///< Input readout, to be overscan/bias/dark corrected
                     pmOverscanOptions *overscanOpts, ///< Options for overscan subtraction, or NULL
-                    const pmReadout *bias, ///< Bias image to subtract, or NULL
-                    const pmReadout *dark ///< Dark image to scale and subtract, or NULL
+                    const pmReadout *bias, ///< Bias to subtract, or NULL
+                    const pmReadout *dark, ///< Dark to scale and subtract, or NULL
+                    const pmFPAview *view ///< View for readout of interest
                    );
 
