Index: /branches/eam_branches/20091201/psModules/src/camera/pmFPAfile.c
===================================================================
--- /branches/eam_branches/20091201/psModules/src/camera/pmFPAfile.c	(revision 26835)
+++ /branches/eam_branches/20091201/psModules/src/camera/pmFPAfile.c	(revision 26836)
@@ -519,4 +519,7 @@
     if (!strcasecmp(type, "SUBKERNEL"))     {
         return PM_FPA_FILE_SUBKERNEL;
+    }
+    if (!strcasecmp(type, "PATTERN")) {
+        return PM_FPA_FILE_PATTERN;
     }
 
@@ -563,4 +566,6 @@
       case PM_FPA_FILE_SUBKERNEL:
         return ("SUBKERNEL");
+      case PM_FPA_FILE_PATTERN:
+        return "PATTERN";
       default:
         return ("NONE");
Index: /branches/eam_branches/20091201/psModules/src/camera/pmFPAfile.h
===================================================================
--- /branches/eam_branches/20091201/psModules/src/camera/pmFPAfile.h	(revision 26835)
+++ /branches/eam_branches/20091201/psModules/src/camera/pmFPAfile.h	(revision 26836)
@@ -49,4 +49,5 @@
     PM_FPA_FILE_SUBKERNEL,
     PM_FPA_FILE_SRCTEXT,
+    PM_FPA_FILE_PATTERN,
 } pmFPAfileType;
 
Index: /branches/eam_branches/20091201/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- /branches/eam_branches/20091201/psModules/src/camera/pmFPAfileIO.c	(revision 26835)
+++ /branches/eam_branches/20091201/psModules/src/camera/pmFPAfileIO.c	(revision 26836)
@@ -43,4 +43,5 @@
 #include "pmFPAConstruct.h"
 #include "pmSubtractionIO.h"
+#include "pmPatternIO.h"
 #include "pmConcepts.h"
 #include "pmConfigRun.h"
@@ -66,5 +67,5 @@
 
         switch (place) {
-	  case PM_FPA_BEFORE:
+          case PM_FPA_BEFORE:
             if (!pmFPAfileRead (file, view, config)) {
                 psError(PS_ERR_IO, false, "failed READ in FPA_BEFORE block for %s", file->name);
@@ -76,6 +77,6 @@
             }
             break;
-	  case PM_FPA_AFTER:
-	    if (!pmFPAfileWrite (file, view, config)) {
+          case PM_FPA_AFTER:
+            if (!pmFPAfileWrite (file, view, config)) {
                 psError(PS_ERR_IO, false, "failed WRITE in FPA_AFTER block for %s", file->name);
                 goto failure;
@@ -86,5 +87,5 @@
             }
             break;
-	  default:
+          default:
             psAbort("You can't get here");
         }
@@ -202,4 +203,7 @@
         status = pmSubtractionReadKernels(view, file, config);
         break;
+      case PM_FPA_FILE_PATTERN:
+        status = pmPatternRead(view, file, config);
+        break;
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -273,23 +277,25 @@
       case PM_FPA_FILE_VARIANCE:
       case PM_FPA_FILE_FRINGE:
-      case PM_FPA_FILE_DARK: {
-          // create FPA structure component based on view
-          psMetadata *format = file->format; // Camera format configuration
-          if (!format) {
-              format = config->format;
-          }
-
-          pmFPA *nameSource = file->src; // Source of FPA.OBS
-          if (!nameSource) {
-              nameSource = file->fpa;
-          }
-          bool mdok;                  // Status of MD lookup
-          const char *fpaObs = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.OBS"); // Obs. id
-
-          pmFPAAddSourceFromView(file->fpa, fpaObs, view, format);
-          psTrace ("psModules.camera", 5, "created fpa data elements for %s (%s) (%d:%d:%d)\n",
-                   file->name, file->name, view->chip, view->cell, view->readout);
-          break;
-      }
+      case PM_FPA_FILE_DARK:
+      case PM_FPA_FILE_PATTERN:
+        {
+            // create FPA structure component based on view
+            psMetadata *format = file->format; // Camera format configuration
+            if (!format) {
+                format = config->format;
+            }
+
+            pmFPA *nameSource = file->src; // Source of FPA.OBS
+            if (!nameSource) {
+                nameSource = file->fpa;
+            }
+            bool mdok;                  // Status of MD lookup
+            const char *fpaObs = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.OBS"); // Obs. id
+
+            pmFPAAddSourceFromView(file->fpa, fpaObs, view, format);
+            psTrace ("psModules.camera", 5, "created fpa data elements for %s (%s) (%d:%d:%d)\n",
+                     file->name, file->name, view->chip, view->cell, view->readout);
+            break;
+        }
       case PM_FPA_FILE_HEADER:
         psAbort ("Create not defined for HEADER");
@@ -462,4 +468,7 @@
         status = pmSubtractionWriteKernels(view, file, config);
         break;
+      case PM_FPA_FILE_PATTERN:
+        status = pmPatternWrite(view, file, config);
+        break;
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -543,4 +552,5 @@
       case PM_FPA_FILE_DARK:
       case PM_FPA_FILE_SUBKERNEL:
+      case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_CMF:
       case PM_FPA_FILE_WCS:
@@ -610,4 +620,5 @@
         break;
       case PM_FPA_FILE_SUBKERNEL:
+      case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_SX:
       case PM_FPA_FILE_RAW:
@@ -771,4 +782,5 @@
       case PM_FPA_FILE_DARK:
       case PM_FPA_FILE_SUBKERNEL:
+      case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_CMF:
       case PM_FPA_FILE_WCS:
@@ -962,4 +974,8 @@
       case PM_FPA_FILE_SUBKERNEL:
         status = pmSubtractionWritePHU(view, file, config);
+        break;
+      case PM_FPA_FILE_PATTERN:
+        status = pmPatternWritePHU(view, file, config);
+        break;
       case PM_FPA_FILE_CMF:
         status = pmSource_CMF_WritePHU (view, file, config);
Index: /branches/eam_branches/20091201/psModules/src/detrend/Makefile.am
===================================================================
--- /branches/eam_branches/20091201/psModules/src/detrend/Makefile.am	(revision 26835)
+++ /branches/eam_branches/20091201/psModules/src/detrend/Makefile.am	(revision 26836)
@@ -17,5 +17,6 @@
 	pmDark.c \
 	pmRemnance.c \
-	pmPattern.c
+	pmPattern.c \
+	pmPatternIO.c
 
 #	pmSkySubtract.c
@@ -35,5 +36,6 @@
 	pmDark.h \
 	pmRemnance.h \
-	pmPattern.h
+	pmPattern.h \
+	pmPatternIO.h
 
 #	pmSkySubtract.h
Index: /branches/eam_branches/20091201/psModules/src/detrend/pmPattern.c
===================================================================
--- /branches/eam_branches/20091201/psModules/src/detrend/pmPattern.c	(revision 26835)
+++ /branches/eam_branches/20091201/psModules/src/detrend/pmPattern.c	(revision 26836)
@@ -1,3 +1,4 @@
 #include <stdio.h>
+#include <string.h>
 #include <pslib.h>
 
@@ -9,5 +10,5 @@
 // Mask a row as bad
 static void patternMaskRow(pmReadout *ro, // Readout to mask
-                           int y,         // Row to mask
+                           int y,       // Row to mask
                            psImageMaskType bad // Mask value to give
                            )
@@ -17,5 +18,5 @@
     psAssert(y < image->numRows, "Row not in image");
 
-    int numCols = image->numCols;       // Size of image
+    int numCols = image->numCols;       // Number of columns
     for (int x = 0; x < numCols; x++) {
         image->data.F32[y][x] = NAN;
@@ -29,4 +30,8 @@
     return;
 }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Measurement and application
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 bool pmPatternRow(pmReadout *ro, int order, int iter, float rej, float thresh,
@@ -80,4 +85,7 @@
     psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, order); // Polynomial to fit
     psVector *data = psVectorAlloc(numCols, PS_TYPE_F32); // Data to fit
+    psArray *corrs = psArrayAlloc(numRows); // Correction parameters
+
+    psImage *corr = psImageAlloc(order + 1, numRows, PS_TYPE_F64); // Corrections applied
 
     for (int y = 0; y < numRows; y++) {
@@ -105,4 +113,8 @@
             continue;
         }
+
+        memcpy(corr->data.F64[y], poly->coeff, (order + 1) * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+        corr->data.F64[y][0] -= background;
+
         psVector *solution = psPolynomial1DEvalVector(poly, indices); // Solution vector
         if (!solution) {
@@ -118,4 +130,8 @@
         psFree(solution);
     }
+
+    psMetadataAddImage(ro->analysis, PS_LIST_TAIL, PM_PATTERN_ROW_CORRECTION, PS_META_REPLACE,
+                       "Pattern row correction", corr);
+    psFree(corrs);
 
     psFree(indices);
@@ -128,5 +144,47 @@
 }
 
-
+bool pmPatternRowApply(pmReadout *ro, psImageMaskType maskBad)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_IMAGE(ro, false);
+
+    bool mdok;                          // Status of MD lookup
+    psImage *corr = psMetadataLookupPtr(&mdok, ro->analysis, PM_PATTERN_ROW_CORRECTION); // Correction
+    if (!mdok) {
+        // No correction to apply
+        return true;
+    }
+
+    psImage *image = ro->image; // Image of interest
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, numCols - 1); // Polynomial to apply
+    psVector *indices = psVectorAlloc(numCols, PS_TYPE_F32); // Indices for polynomial
+    float norm = 2.0 / (float)numCols;  // Normalisation for indices
+    for (int x = 0; x < numCols; x++) {
+        indices->data.F32[x] = x * norm - 1.0;
+    }
+
+    for (int y = 0; y < numRows; y++) {
+        memcpy(poly->coeff, corr->data.F64[y], numCols * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+        psVector *solution = psPolynomial1DEvalVector(poly, indices); // Solution vector
+        if (!solution) {
+            psWarning("Unable to evaluate polynomial for row %d", y);
+            psErrorClear();
+            patternMaskRow(ro, y, maskBad);
+            continue;
+        }
+
+        for (int x = 0; x < numCols; x++) {
+            image->data.F32[y][x] -= solution->data.F32[x];
+        }
+        psFree(solution);
+    }
+
+    psFree(poly);
+    psFree(indices);
+
+    return true;
+}
 
 
@@ -255,4 +313,6 @@
             psImageInit(ro->image, NAN);
             psBinaryOp(ro->mask, ro->mask, "|", psScalarAlloc(maskBad, PS_TYPE_IMAGE_MASK));
+            psMetadataAddF32(ro->analysis, PS_LIST_TAIL, PM_PATTERN_CELL_CORRECTION, PS_META_REPLACE,
+                             "Pattern cell correction solution", NAN);
             continue;
         }
@@ -262,4 +322,6 @@
                  cellName, correction);
         psBinaryOp(ro->image, ro->image, "+", psScalarAlloc(correction, PS_TYPE_F32));
+        psMetadataAddF32(ro->analysis, PS_LIST_TAIL, PM_PATTERN_CELL_CORRECTION, PS_META_REPLACE,
+                         "Pattern cell correction solution", correction);
     }
 
@@ -269,2 +331,43 @@
     return true;
 }
+
+bool pmPatternCellApply(pmReadout *ro, psImageMaskType maskBad)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_IMAGE(ro, false);
+
+    bool mdok;                          // Status of MD lookup
+    float corr = psMetadataLookupF32(&mdok, ro->analysis, PM_PATTERN_CELL_CORRECTION); // Correction to apply
+    if (!mdok) {
+        // No correction to apply
+        return true;
+    }
+
+    psImage *image = ro->image, *mask = ro->mask; // Image and mask of interest
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    if (!isfinite(corr)) {
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                image->data.F32[y][x] = NAN;
+            }
+        }
+        if (mask) {
+            for (int y = 0; y < numRows; y++) {
+                for (int x = 0; x < numCols; x++) {
+                    mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= maskBad;
+                }
+            }
+        }
+    } else {
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                image->data.F32[y][x] += corr;
+            }
+        }
+    }
+
+    return true;
+}
+
+
Index: /branches/eam_branches/20091201/psModules/src/detrend/pmPattern.h
===================================================================
--- /branches/eam_branches/20091201/psModules/src/detrend/pmPattern.h	(revision 26835)
+++ /branches/eam_branches/20091201/psModules/src/detrend/pmPattern.h	(revision 26836)
@@ -18,4 +18,7 @@
 /// @{
 
+#define PM_PATTERN_ROW_CORRECTION "PATTERN.ROW.CORRECTION" // Pattern row correction on analysis metadata
+#define PM_PATTERN_CELL_CORRECTION "PATTERN.CELL.CORRECTION" // Pattern cell correction on analysis metadata
+
 /// Fit and remove pattern noise over rows
 bool pmPatternRow(
@@ -31,4 +34,9 @@
     );
 
+/// Apply previously measured row pattern correction
+bool pmPatternRowApply(pmReadout *ro,   ///< Readout to correct
+                       psImageMaskType maskBad ///< Mask value to give bad pixels
+                       );
+
 /// Fix the background on cells known to be troublesome
 bool pmPatternCell(
@@ -41,4 +49,9 @@
     );
 
+/// Apply previously measured cell pattern correction
+bool pmPatternCellApply(pmReadout *ro,          ///< Readout to correct
+                        psImageMaskType maskBad ///< Mask value to give bad pixels
+                        );
+
 
 /// @}
Index: /branches/eam_branches/20091201/psModules/src/detrend/pmPatternIO.c
===================================================================
--- /branches/eam_branches/20091201/psModules/src/detrend/pmPatternIO.c	(revision 26836)
+++ /branches/eam_branches/20091201/psModules/src/detrend/pmPatternIO.c	(revision 26836)
@@ -0,0 +1,456 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <string.h>
+
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfileFitsIO.h"
+#include "pmConceptsRead.h"
+
+#include "pmPattern.h"
+#include "pmPatternIO.h"
+
+#define ARRAY_BUFFER 16                 // Number to add to array at a time
+
+// Names of FITS table columns
+#define NAME_XMIN "XMIN"                // Region of applicability: minimum x value
+#define NAME_XMAX "XMAX"                // Region of applicability: maximum x value
+#define NAME_YMIN "YMIN"                // Region of applicability: minimum y value
+#define NAME_YMAX "YMAX"                // Region of applicability: maximum y value
+#define NAME_KERNEL "KERNEL"            // Kernel description
+#define NAME_TYPE "TYPE"                // Kernel type
+#define NAME_SIZE "SIZE"                // Kernel half-size
+#define NAME_INNER "INNER"              // Size of inner region (only applicable for some kernel types)
+#define NAME_SPATIAL "SPATIAL_ORDER"    // Order of spatial polynomial
+#define NAME_BG "BG_ORDER"              // Order of background polynomial
+#define NAME_MODE "MODE"                // Matching mode
+#define NAME_COLS "COLUMNS"             // Number of columns
+#define NAME_ROWS "ROWS"                // Number of rows
+#define NAME_SOL1 "SOLUTION_1"          // Solution for convolving image 1
+#define NAME_SOL2 "SOLUTION_2"          // Solution for convolving image 2
+#define NAME_MEAN "MEAN"                // Mean of chi^2 from stamps
+#define NAME_RMS  "RMS"                 // RMS of chi^2 from stamps
+#define NAME_NUMSTAMPS "NUMSTAMPS"      // Number of good stamps
+
+#define EXTNAME_KERNEL "SUBTRACTION_KERNEL"    // Extension name for kernel
+#define EXTNAME_IMAGE  "SUBTRACTION_KERNEL_IMAGE"    // Extension name for image
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutWritePattern(pmReadout *ro, psFits *fits)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    bool gotRow, gotCell;
+    psImage *rowCorr = psMetadataLookupPtr(&gotRow, ro->analysis, PM_PATTERN_ROW_CORRECTION); // Row correction
+    float cellCorr = psMetadataLookupF32(&gotCell, ro->analysis, PM_PATTERN_CELL_CORRECTION); // Cell corr.
+    if (!gotRow && !gotCell) {
+        // Nothing to write
+        return true;
+    }
+
+    pmCell *cell = ro->parent;          // Cell of interest
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No cell associated with readout.");
+        return false;
+    }
+    pmChip *chip = cell->parent;    // Chip of interest
+    pmFPA *fpa = chip->parent;      // FPA of interest
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // HDU for readout
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No HDU associated with readout.");
+        return false;
+    }
+
+    psMetadata *header = psMetadataCopy(NULL, hdu->header); // Header for output
+
+    if (gotCell) {
+        psMetadataAddF32(header, PS_LIST_TAIL, PM_PATTERN_CELL_CORRECTION, PS_META_REPLACE,
+                         "Pattern cell correction value", cellCorr);
+    }
+
+    if (gotRow) {
+        if (!psFitsWriteImage(fits, header, rowCorr, 0, hdu->extname)) {
+            psError(PS_ERR_IO, false, "Unable to write pattern row correction.");
+            psFree(header);
+            return false;
+        }
+    } else {
+        if (!psFitsWriteBlank(fits, header, hdu->extname)) {
+            psError(PS_ERR_IO, false, "Unable to write pattern cell correction.");
+            psFree(header);
+            return false;
+        }
+    }
+
+    psFree(header);
+    return true;
+}
+
+static bool pmCellWritePattern(pmCell *cell, const pmFPAview *view,
+                               pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        thisView->readout = i;
+        if (!pmReadoutWritePattern(readout, file->fits)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth readout", i);
+            psFree(thisView);
+            return false;
+        }
+    }
+    psFree(thisView);
+    return true;
+}
+
+static bool pmChipWritePattern(pmChip *chip, const pmFPAview *view,
+                               pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        thisView->cell = i;
+        if (!pmCellWritePattern(cell, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth cell", i);
+            psFree(thisView);
+            return false;
+        }
+    }
+    psFree(thisView);
+    return true;
+}
+
+static bool pmFPAWritePattern(pmFPA *fpa, const pmFPAview *view,
+                              pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        if (!pmChipWritePattern(chip, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth chip", i);
+            psFree(thisView);
+            return false;
+        }
+    }
+    psFree(thisView);
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+bool pmReadoutReadPattern(pmReadout *ro, psFits *fits)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    pmCell *cell = ro->parent;          // Cell of interest
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No cell associated with readout.");
+        return false;
+    }
+    pmChip *chip = cell->parent;    // Chip of interest
+    pmFPA *fpa = chip->parent;      // FPA of interest
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // HDU for readout
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "No HDU associated with readout.");
+        return false;
+    }
+
+    if (!psFitsMoveExtName(fits, hdu->extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to pattern correction.");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // Header
+    if (!header) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to read header for pattern correction.");
+        return false;
+    }
+
+    psMetadataItem *cellCorr = psMetadataLookup(header, PM_PATTERN_CELL_CORRECTION); // Cell pattern correction
+    if (cellCorr) {
+        psMetadataAddItem(ro->analysis, cellCorr, PS_LIST_TAIL, PS_META_REPLACE);
+    }
+
+    int naxis = psMetadataLookupS32(NULL, header, "NAXIS"); // Number of axes
+    if (naxis > 0) {
+        psImage *rowCorr = psFitsReadImage(fits, psRegionSet(0, 0, 0, 0), 0); // Row pattern correction
+        psMetadataAddImage(ro->analysis, PS_LIST_TAIL, PM_PATTERN_ROW_CORRECTION, PS_META_REPLACE,
+                           "Pattern row correction", rowCorr);
+        psFree(rowCorr);
+    }
+
+    psFree(header);
+
+    return true;
+}
+
+static bool pmCellReadPattern(pmCell *cell, const pmFPAview *view,
+                              pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    // Create a readout if none exists
+    if (!cell->readouts || cell->readouts->n == 0) {
+        pmReadout *readout = pmReadoutAlloc(cell); // New readout
+        psFree(readout);                // Drop reference
+    }
+
+    cell->data_exists = false;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        thisView->readout = i;
+        pmReadoutReadPattern(readout, file->fits);
+        if (!readout->data_exists) {
+            continue;
+        }
+#if 0
+        // load in the concept information for this cell
+        if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+            psErrorClear();
+            psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+        }
+#endif
+        cell->data_exists = true;
+    }
+    psFree(thisView);
+
+    return true;
+}
+
+static bool pmChipReadPattern(pmChip *chip, const pmFPAview *view,
+                              pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    chip->data_exists = false;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        thisView->cell = i;
+        pmCellReadPattern(cell, thisView, file, config);
+        if (!cell->data_exists) {
+            continue;
+        }
+        chip->data_exists = true;
+    }
+    psFree(thisView);
+
+#if 0
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for chip.\n");
+        return false;
+    }
+#endif
+
+    return true;
+}
+
+static bool pmFPAReadPattern(pmFPA *fpa, const pmFPAview *view,
+                             pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa->chips, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc(view->nRows); // Copy of input view
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        pmChipReadPattern(chip, thisView, file, config);
+    }
+    psFree(thisView);
+
+#if 0
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for fpa.\n");
+        return false;
+    }
+#endif
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmPatternWrite(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config, false); // Suitable FPA for writing
+
+    if (view->chip == -1) {
+        if (!pmFPAWritePattern(fpa, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write pattern correction from fpa");
+            psFree(fpa);
+            return false;
+        }
+        psFree(fpa);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
+        psFree(fpa);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        if (!pmChipWritePattern(chip, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write pattern correction from chip");
+            psFree(fpa);
+            return false;
+        }
+        psFree(fpa);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing cell == %d (>= cells->n == %ld)",
+                view->cell, chip->cells->n);
+        psFree(fpa);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        if (!pmCellWritePattern(cell, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write pattern correction from cell");
+            psFree(fpa);
+            return false;
+        }
+        psFree(fpa);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing readout == %d (>= readouts->n == %ld)",
+                view->readout, cell->readouts->n);
+        psFree(fpa);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (!pmReadoutWritePattern(readout, file->fits)) {
+        psError(PS_ERR_IO, false, "Failed to write pattern correction from readout %d", view->readout);
+        psFree(fpa);
+        return false;
+    }
+
+    psFree(fpa);
+    return true;
+}
+
+bool pmPatternWritePHU(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    if (file->wrote_phu) {
+        return true;
+    }
+
+    // find the FPA phu
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config, false); // Suitable FPA for writing
+    pmHDU *phu = psMemIncrRefCounter(pmFPAviewThisPHU(view, fpa));
+    psFree(fpa);
+
+    // if there is no PHU, this is a single header+image (extension-less) file. This could be the case for an
+    // input SPLIT set of files being written out as a MEF.  if there is a PHU, write it out as a 'blank'
+    psMetadata *outhead = psMetadataAlloc();
+    if (phu) {
+        psMetadataCopy (outhead, phu->header);
+    }
+    psFree(phu);
+
+    pmConfigConformHeader(outhead, file->format);
+
+    psFitsWriteBlank(file->fits, outhead, "");
+    file->wrote_phu = true;
+
+    psTrace("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+    psFree(outhead);
+
+    return true;
+}
+
+bool pmPatternRead(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAReadPattern(fpa, view, file, config);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipReadPattern(chip, view, file, config);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellReadPattern(cell, view, file, config);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    return pmReadoutReadPattern(readout, file->fits);
+}
Index: /branches/eam_branches/20091201/psModules/src/detrend/pmPatternIO.h
===================================================================
--- /branches/eam_branches/20091201/psModules/src/detrend/pmPatternIO.h	(revision 26836)
+++ /branches/eam_branches/20091201/psModules/src/detrend/pmPatternIO.h	(revision 26836)
@@ -0,0 +1,37 @@
+#ifndef PM_PATTERN_IO_H
+#define PM_PATTERN_IO_H
+
+#include <pslib.h>
+
+#include <pmHDU.h>
+#include <pmFPA.h>
+
+/// Write pattern correction within a readout to a FITS file
+bool pmReadoutWritePattern(
+    pmReadout *readout,                 ///< Readout for which to write pattern correction (in analysis MD)
+    psFits *fits                        ///< FITS file to which to write
+    );
+
+
+bool pmReadoutReadPattern(
+    pmReadout *readout,                 ///< Readout for which to read pattern correction (into analysis MD)
+    psFits *fits                        ///< FITS file to which to write
+    );
+
+
+bool pmPatternRead(const pmFPAview *view, ///< View into which to read
+                   pmFPAfile *file, ///< File from which to read
+                   pmConfig *config ///< Configuration
+    );
+
+bool pmPatternWrite(const pmFPAview *view, ///< View from which to write
+                    pmFPAfile *file, ///< File to which to write
+                    pmConfig *config ///< Configuration
+    );
+
+bool pmPatternWritePHU(const pmFPAview *view, // View to PHU
+                       pmFPAfile *file, ///< File to which to write
+                       pmConfig *config ///< Configuration
+    );
+
+#endif
Index: /branches/eam_branches/20091201/psModules/src/psmodules.h
===================================================================
--- /branches/eam_branches/20091201/psModules/src/psmodules.h	(revision 26835)
+++ /branches/eam_branches/20091201/psModules/src/psmodules.h	(revision 26836)
@@ -79,4 +79,5 @@
 #include <pmRemnance.h>
 #include <pmPattern.h>
+#include <pmPatternIO.h>
 
 // the following headers are from psModule:astrom
