Index: /trunk/ppImage/src/Makefile.am
===================================================================
--- /trunk/ppImage/src/Makefile.am	(revision 9856)
+++ /trunk/ppImage/src/Makefile.am	(revision 9857)
@@ -4,4 +4,5 @@
 	ppImage.h \
 	ppImageOptions.h \
+	ppImageDetrendFringe.h \
 	ppMem.h
 
@@ -18,4 +19,5 @@
 	ppImageDetrendBias.c \
 	ppImageDetrendNonLinear.c \
+	ppImageDetrendFringe.c \
 	ppImageRebinReadout.c \
 	ppImageMosaic.c \
@@ -38,4 +40,5 @@
 	ppImageDetrendBias.c \
 	ppImageDetrendNonLinear.c \
+	ppImageDetrendFringe.c \
 	ppImageRebinReadout.c \
 	ppImageMosaic.c \
Index: /trunk/ppImage/src/ppImageDetrendFringe.c
===================================================================
--- /trunk/ppImage/src/ppImageDetrendFringe.c	(revision 9857)
+++ /trunk/ppImage/src/ppImageDetrendFringe.c	(revision 9857)
@@ -0,0 +1,142 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppImageDetrendFringe.h"
+
+bool ppImageDetrendFringeMeasure(pmFringeStats **science, // (Ptr to) science fringe measurements
+                                 psArray *references, // Array of reference fringe measurements
+                                 const pmReadout *readout, // Readout to measure
+                                 const ppImageOptions *options // Options
+                                 )
+{
+    assert(science);
+    PS_ASSERT_ARRAY_NON_NULL(references, false);
+
+    pmFringeStats *reference = references->data[0]; // Take the first as representative
+    pmFringeRegions *regions = reference->regions; // Regions to measure
+    pmFringeStats *fringe = pmFringeStatsMeasure(regions, readout, options->maskValue); // Fringe stats
+
+    // Normalise measurements by the exposure time
+    bool mdok;                          // Status of MD lookup
+    float expTime = psMetadataLookupF32(&mdok, readout->parent->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!mdok || !isfinite(expTime)) {
+        psError(PS_ERR_UNKNOWN, false, "CELL.EXPOSURE is not set for --- can't normalise fringes\n");
+        psFree(fringe);
+        return false;
+    }
+    if (expTime == 0) {
+        psWarning("Exposure time is zero --- are you sure you want fringe subtraction?\n");
+        expTime = 1.0;
+    }
+    psBinaryOp(fringe->f, fringe->f, "*", psScalarAlloc(1.0 / expTime, PS_TYPE_F32));
+    psBinaryOp(fringe->df, fringe->df, "*", psScalarAlloc(1.0 / expTime, PS_TYPE_F32));
+
+    if (!*science) {
+        // Only a single readout
+        *science = fringe;
+    } else {
+        // Multiple readouts: concatenate
+        psArray *concatenate = psArrayAlloc(2); // Array to hold fringes
+
+        // Concatenate science measurements
+        concatenate->data[0] = *science;
+        concatenate->data[1] = fringe;
+        pmFringeStats *sciNew = pmFringeStatsConcatenate(concatenate, NULL, NULL); // New measurements
+        psFree(*science);
+        *science = sciNew;
+
+        // Concatenate reference measurements
+        for (int i = 0; i < references->n; i++) {
+            concatenate->data[0] = concatenate->data[1] = references->data[i];
+            pmFringeStats *refNew = pmFringeStatsConcatenate(concatenate, NULL, NULL);
+            psFree(references->data[i]);
+            references->data[i] = refNew;
+        }
+
+        concatenate->data[0] = concatenate->data[1] = NULL;
+        psFree(concatenate);
+    }
+
+    return true;
+}
+
+
+
+// Solve the fringe system: we have science fringe measurements for each cell, and an array of reference
+// fringe measurements for each cell.  Need to concatenate these together first, and then solve.
+pmFringeScale *ppImageDetrendFringeSolve(psArray *science, // Science fringe measurements (per cell)
+                                         psArray *references, // Array of array of fringe measurements
+                                         const ppImageOptions *options // Options
+    )
+{
+    pmFringeStats *scienceCat = pmFringeStatsConcatenate(science, NULL, NULL); // Science fringes
+
+    // Need to transform the array of cells each with an array of fringes --> array of fringes for the chip as
+    // a whole
+    int numRefs = ((psArray*)references->data[0])->n; // Number of reference fringes
+    psArray *referencesCat = psArrayAlloc(numRefs); // Reference fringes
+    for (int i = 0; i < numRefs; i++) { // Iterate over fringes
+        psArray *refs = psArrayAlloc(references->n); // Array of fringes for each cell
+        for (int j = 0; j < references->n; j++) { // Iterate over cells
+            psArray *ref = references->data[j]; // Array of references for this cell
+            refs->data[j] = psMemIncrRefCounter(ref->data[i]);
+        }
+        referencesCat->data[i] = pmFringeStatsConcatenate(refs, NULL, NULL);
+        psFree(refs);
+    }
+
+    // Now we can solve
+    psTrace("ppImage", 3, "Solving fringe system...\n");
+    pmFringeScale *solution = pmFringeScaleMeasure(scienceCat, referencesCat, options->fringeRej,
+                                                   options->fringeIter, options->fringeKeep);
+
+    psFree(scienceCat);
+    psFree(referencesCat);
+
+    return solution;
+}
+
+
+psImage *ppImageDetrendFringeGenerate(pmCell *science, // Science cell
+                                      pmCell *fringes, // Fringe cell, one readout per fringe component
+                                      const pmFringeScale *solution // Fringe solution to apply
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(science, false);
+    PS_ASSERT_PTR_NON_NULL(solution, false);
+    assert(fringes->readouts->n == solution->nFringeFrames);
+
+    bool mdok;                          // Status of MD lookup
+    float expTime = psMetadataLookupF32(&mdok, science->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!mdok || !isfinite(expTime)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.EXPOSURE is not set --- can't renormalise fringes\n");
+        return false;
+    }
+    if (expTime == 0) {
+        psWarning("Exposure time is zero --- are you sure you want fringe subtraction?\n");
+        expTime = 1.0;
+    }
+
+    // Construct the fringe image from the scale
+    psTrace("ppImage", 3, "Generating fringe correction...\n");
+    psImage *sumFringe = NULL; // Sum of the fringes
+    for (int i = 0; i < solution->nFringeFrames; i++) {
+        pmReadout *fringeRO = fringes->readouts->data[i]; // Fringe readout
+        psImage *fringe = fringeRO->image; // Fringe image
+        psTrace("ppImage", 5, "Scale for fringe component %d is %f\n",
+                i, solution->coeff->data.F32[i + 1]);
+        psBinaryOp(fringe, fringe, "*", psScalarAlloc(solution->coeff->data.F32[i + 1], PS_TYPE_F32));
+        if (!sumFringe) {
+            sumFringe = psImageCopy(NULL, fringe, PS_TYPE_F32);
+        } else {
+            psBinaryOp(sumFringe, sumFringe, "+", fringe);
+        }
+    }
+    if (expTime != 1.0) {
+        psBinaryOp(sumFringe, sumFringe, "*", psScalarAlloc(expTime, PS_TYPE_F32));
+    }
+
+    return sumFringe;
+}
+
Index: /trunk/ppImage/src/ppImageDetrendFringe.h
===================================================================
--- /trunk/ppImage/src/ppImageDetrendFringe.h	(revision 9857)
+++ /trunk/ppImage/src/ppImageDetrendFringe.h	(revision 9857)
@@ -0,0 +1,24 @@
+#ifndef PP_IMAGE_DETREND_FRINGE_H
+#define PP_IMAGE_DETREND_FRINGE_H
+
+#include <pslib.h>
+#include <psmodules.h>
+#include "ppImageOptions.h"
+
+bool ppImageDetrendFringeMeasure(pmFringeStats **science, // (Ptr to) science fringe measurements
+                                 psArray *references, // Array of reference fringe measurements
+                                 const pmReadout *readout, // Readout to measure
+                                 const ppImageOptions *options // Options
+    );
+
+pmFringeScale *ppImageDetrendFringeSolve(psArray *science, // Science fringe measurements (per cell)
+                                         psArray *references, // Array of array of fringe measurements
+                                         const ppImageOptions *options // Options
+    );
+
+psImage *ppImageDetrendFringeGenerate(pmCell *science, // Science cell
+                                      pmCell *fringes, // Fringe cell, one readout per fringe component
+                                      const pmFringeScale *solution // Fringe solution to apply
+    );
+
+#endif
Index: /trunk/ppImage/src/ppImageLoop.c
===================================================================
--- /trunk/ppImage/src/ppImageLoop.c	(revision 9856)
+++ /trunk/ppImage/src/ppImageLoop.c	(revision 9857)
@@ -5,4 +5,5 @@
 #include <ppStats.h>
 #include "ppImage.h"
+#include "ppImageDetrendFringe.h"
 
 bool ppImageLoop (pmConfig *config, ppImageOptions *options) {
@@ -44,8 +45,30 @@
         if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) return false;
 
+        const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+        psArray *fringeRef = NULL;      // Array of array of fringe statistics for reference --- for each cell
+        psArray *fringeSci = NULL;      // Array of fringe statistics for science --- for each cell
+        if (options->doFringe) {
+            fringeRef = psArrayAlloc(chip->cells->n);
+            fringeSci = psArrayAlloc(chip->cells->n);
+        }
+
         while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
             psLogMsg ("ppImageLoop", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
             if (!cell->process || !cell->file_exists) { continue; }
             if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) return false;
+            const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+
+            if (options->doFringe) {
+                pmFPAfile *fringeFile = psMetadataLookupPtr(&status, config->files, "PPIMAGE.FRINGE");
+                if (!status || !fringeFile) {
+                    psErrorStackPrint(stderr, "Can't find fringe data!\n");
+                    exit(EXIT_FAILURE);
+                }
+
+                psString fringeExt = NULL; // Fringe extension name
+                psStringAppend(&fringeExt, "FRINGE_%s_%s", chipName, cellName);
+                fringeRef->data[view->cell] = pmFringesReadFits(NULL, fringeFile->fits, fringeExt);
+                psFree(fringeExt);
+            }
 
             // process each of the readouts
@@ -57,6 +80,40 @@
                 if (!ppImageDetrendReadout (config, options, view)) return false;
 
+                if (options->doFringe) {
+                    ppImageDetrendFringeMeasure((pmFringeStats**)(&fringeSci->data[view->cell]),
+                                                fringeRef->data[view->cell], readout, options);
+                }
+            }
+        }
+
+        // Solve the fringe system
+        pmFringeScale *fringeSoln = NULL; // Solution for the fringes
+        if (options->doFringe) {
+            fringeSoln = ppImageDetrendFringeSolve(fringeSci, fringeRef, options);
+            psFree(fringeSci);
+            psFree(fringeRef);
+        }
+
+        // Go back over the cells to apply the fringe and do statistics
+        view->cell = view->readout = -1;
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            // Apply the fringe correction
+            psTrace("ppImage", 3, "Applying fringe correction...\n");
+            bool mdok;                          // Status of MD lookup
+            pmFPAfile *fringeFile = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.FRINGE");
+            pmCell *fringeCell = pmFPAviewThisCell(view, fringeFile->fpa);
+            psImage *fringe = ppImageDetrendFringeGenerate(cell, fringeCell, fringeSoln);
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                if (!readout->data_exists) { continue; }
+
+                // XXX: Make generic, so subregions may be subtracted as well
+                psBinaryOp(readout->image, readout->image, "-", fringe);
+
                 if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) return false;
             }
+            psFree(fringe);
+
 
             // Perform statistics on the detrended cell
@@ -67,4 +124,5 @@
             if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) return false;
         }
+        psFree(fringeSoln);
 
         ppImageMosaicChip (config, view, "PPIMAGE.OUTPUT.CHIP", "PPIMAGE.OUTPUT");
Index: /trunk/ppImage/src/ppImageOptions.c
===================================================================
--- /trunk/ppImage/src/ppImageOptions.c	(revision 9856)
+++ /trunk/ppImage/src/ppImageOptions.c	(revision 9857)
@@ -49,4 +49,9 @@
     // Overscan defaults
     options->overscan      = NULL;      // Overscan options
+
+    // Fringe defaults
+    options->fringeRej = NAN;
+    options->fringeIter = 0;
+    options->fringeKeep = 1.0;
 
     // Non-linearity default options
@@ -163,4 +168,5 @@
     options->doDark = psMetadataLookupBool(NULL, recipe, "DARK");
     options->doFlat = psMetadataLookupBool(NULL, recipe, "FLAT");
+    options->doFringe = psMetadataLookupBool(NULL, recipe, "FRINGE");
     options->doShutter = psMetadataLookupBool(NULL, recipe, "SHUTTER");
 
@@ -200,4 +206,9 @@
     }
 
+    // Fringe options
+    options->fringeRej = psMetadataLookupF32(NULL, recipe, "FRINGE.REJ");
+    options->fringeIter = psMetadataLookupS32(NULL, recipe, "FRINGE.ITER");
+    options->fringeKeep = psMetadataLookupF32(NULL, recipe, "FRINGE.KEEP");
+
     return options;
 }
Index: /trunk/ppImage/src/ppImageOptions.h
===================================================================
--- /trunk/ppImage/src/ppImageOptions.h	(revision 9856)
+++ /trunk/ppImage/src/ppImageOptions.h	(revision 9857)
@@ -38,4 +38,8 @@
     int xBin2, yBin2;
 
+    float fringeRej;                    // Fringe rejection limit
+    int fringeIter;                     // Fringe iterations
+    float fringeKeep;                   // Fringe keep fraction
+
 } ppImageOptions;
 
Index: /trunk/ppImage/src/ppImageParseCamera.c
===================================================================
--- /trunk/ppImage/src/ppImageParseCamera.c	(revision 9856)
+++ /trunk/ppImage/src/ppImageParseCamera.c	(revision 9857)
@@ -34,7 +34,7 @@
         pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.BIAS", input->fpa, PM_DETREND_TYPE_BIAS);
         if (!status) {
-	    psError (PS_ERR_IO, false, "can't find a bias image source");
-	    return NULL;
-	}
+            psError (PS_ERR_IO, false, "can't find a bias image source");
+            return NULL;
+        }
     }
     if (options->doDark) {
@@ -44,7 +44,7 @@
         pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.DARK", input->fpa, PM_DETREND_TYPE_DARK);
         if (!status) {
-	    psError (PS_ERR_IO, false, "can't find a dark image source");
-	    return NULL;
-	}
+            psError (PS_ERR_IO, false, "can't find a dark image source");
+            return NULL;
+        }
     }
     if (options->doMask) {
@@ -54,7 +54,7 @@
         pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.MASK", input->fpa, PM_DETREND_TYPE_MASK);
         if (!status) {
-	    psError (PS_ERR_IO, false, "can't find a mask image source");
-	    return NULL;
-	}
+            psError (PS_ERR_IO, false, "can't find a mask image source");
+            return NULL;
+        }
     }
     if (options->doShutter) {
@@ -62,9 +62,9 @@
         pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.SHUTTER", "SHUTTER");
         pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.SHUTTER");
-        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.SHUTTER", input->fpa, PM_DETREND_TYPE_FLAT);
+        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.SHUTTER", input->fpa, PM_DETREND_TYPE_SHUTTER);
         if (!status) {
-	    psError (PS_ERR_IO, false, "can't find a shutter image source");
-	    return NULL;
-	}
+            psError (PS_ERR_IO, false, "can't find a shutter image source");
+            return NULL;
+        }
     }
     if (options->doFlat) {
@@ -74,7 +74,17 @@
         pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.FLAT", input->fpa, PM_DETREND_TYPE_FLAT);
         if (!status) {
-	    psError (PS_ERR_IO, false, "can't find a flat image source");
-	    return NULL;
-	}
+            psError (PS_ERR_IO, false, "can't find a flat image source");
+            return NULL;
+        }
+    }
+    if (options->doFringe) {
+        bool status = false;
+        pmFPAfileDefineFromArgs  (&status, config, "PPIMAGE.FRINGE", "FRINGE");
+        pmFPAfileDefineFromConf  (&status, config, "PPIMAGE.FRINGE");
+        pmFPAfileDefineFromDetDB (&status, config, "PPIMAGE.FRINGE", input->fpa, PM_DETREND_TYPE_FRINGE_IMAGE);
+        if (!status) {
+            psError (PS_ERR_IO, false, "can't find a fringe image source");
+            return NULL;
+        }
     }
 
Index: /trunk/ppImage/src/ppImageParseDetrend.c
===================================================================
--- /trunk/ppImage/src/ppImageParseDetrend.c	(revision 9856)
+++ /trunk/ppImage/src/ppImageParseDetrend.c	(revision 9857)
@@ -54,4 +54,11 @@
     }
 
+    if (options->doFringe) {
+        bool status = false;
+        pmFPAfileFromArgs (&status, config, "PPIMAGE.FRINGE", "FRINGE");
+        pmFPAfileFromConf (&status, config, "PPIMAGE.FRINGE", input->fpa);
+        if (!status) psAbort ("ppImageParseDetrend", "can't find a fringe image source");
+    }
+
     // the following files are output targets
     // XXX which of these are required?
