Index: /trunk/ppImage/src/Makefile.am
===================================================================
--- /trunk/ppImage/src/Makefile.am	(revision 23844)
+++ /trunk/ppImage/src/Makefile.am	(revision 23845)
@@ -50,4 +50,6 @@
 	ppImageMetadataStats.c \
 	ppImageReplaceBackground.c \
+	ppImageMeasureCrosstalk.c \
+	ppImageCorrectCrosstalk.c \
 	ppImageDefineFile.c \
 	ppImageSetMaskBits.c \
Index: /trunk/ppImage/src/ppImage.h
===================================================================
--- /trunk/ppImage/src/ppImage.h	(revision 23844)
+++ /trunk/ppImage/src/ppImage.h	(revision 23845)
@@ -44,4 +44,7 @@
     bool checkCTE;                      // measure pixel-based variance
 
+    bool doCrosstalkMeasure;		// measure crosstalk signal
+    bool doCrosstalkCorrect;		// apply crosstalk correction
+
     // output files requested
     bool BaseFITS;
@@ -185,4 +188,10 @@
 
 
+// measure the crosstalk signal
+bool ppImageMeasureCrosstalk(pmConfig *config, ppImageOptions *options, pmFPAview *view);
+
+// correct the crosstalk signal
+bool ppImageCorrectCrosstalk(pmConfig *config, ppImageOptions *options, pmFPAview *view);
+
 // Measure fringes
 bool ppImageDetrendFringeMeasure(pmReadout *readout, // Readout to measure
Index: /trunk/ppImage/src/ppImageCorrectCrosstalk.c
===================================================================
--- /trunk/ppImage/src/ppImageCorrectCrosstalk.c	(revision 23845)
+++ /trunk/ppImage/src/ppImageCorrectCrosstalk.c	(revision 23845)
@@ -0,0 +1,18 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+#define ESCAPE(MESSAGE) { \
+  psError(PS_ERR_UNKNOWN, false, MESSAGE); \
+  psFree(view); \
+  return false; \
+}
+
+// For the moment, this implementation is VERY GPC-specific
+
+bool ppImageCorrectCrosstalk(pmConfig *config, ppImageOptions *options, pmFPAview *view)
+{
+    return true;
+}
Index: /trunk/ppImage/src/ppImageLoop.c
===================================================================
--- /trunk/ppImage/src/ppImageLoop.c	(revision 23844)
+++ /trunk/ppImage/src/ppImageLoop.c	(revision 23845)
@@ -50,4 +50,18 @@
             ESCAPE("load failure for Chip");
         }
+
+	// crosstalk measurement needs to be done on the entire chip at once, and before
+	// signal levels are modified by the detrending.  If crosstalk measurement is
+	// requested, the read-level for the images is set to CHIP.
+	if (!ppImageMeasureCrosstalk(config, options, view)) {
+	  ESCAPE("Unable to perform crosstalk correction");
+	}
+
+	// crosstalk correction needs to be done on the entire chip at once, and before
+	// signal levels are modified by the detrending.  If crosstalk correction is
+	// requested, the read-level for the images is set to CHIP.
+	if (!ppImageCorrectCrosstalk(config, options, view)) {
+	  ESCAPE("Unable to perform crosstalk correction");
+	}
 
         psTimerStart(TIMER_DETREND);
Index: /trunk/ppImage/src/ppImageMeasureCrosstalk.c
===================================================================
--- /trunk/ppImage/src/ppImageMeasureCrosstalk.c	(revision 23845)
+++ /trunk/ppImage/src/ppImageMeasureCrosstalk.c	(revision 23845)
@@ -0,0 +1,180 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+#define ESCAPE(MESSAGE) {				\
+	psError(PS_ERR_UNKNOWN, false, MESSAGE);	\
+	psFree(view);					\
+	return false;					\
+    }
+
+// For the moment, this implementation is VERY GPC-specific
+
+bool ppImageMeasureCrosstalk(pmConfig *config, ppImageOptions *options, pmFPAview *view)
+{
+    bool status;
+    const float CUTOFF = 60000;
+    const int MIN_MATCHED_PIXELS = 500;  // require a significant area of high signal
+    char name[64];
+
+    if (!options->doCrosstalkMeasure) return true;
+
+    psTimerStart("crosstalk");
+
+    // confirm that this camera is GPC1
+
+    // find the currently selected chip
+    pmChip *chip = pmFPAfileThisChip(config->files, view, "PPIMAGE.INPUT");
+    pmFPA *fpa = chip->parent;
+
+    // for each cell (xyNM), we want to find the pixels with values above a cutoff (40k, 60k, ?)
+    // for all of the other cells in the row (xyJM), we want to measure the (robust) median flux in the pixels
+    // corresponding to the high pixels in xyNM, and compare with the median flux for the cell
+
+    psArray *vectorSet = psArrayAlloc(8);
+    psArray *cellRows = psArrayAlloc(8);
+    psArray *imageRows = psArrayAlloc(8);
+    for (int i = 0; i < 8; i++) {
+	psArray *cellRow = psArrayAlloc(8);
+	cellRows->data[i] = cellRow;
+	psArray *imageRow = psArrayAlloc(8);
+	imageRows->data[i] = imageRow;
+	psVector *vector = psVectorAllocEmpty(100, PS_TYPE_F32);
+	vectorSet->data[i] = vector;
+    }
+
+    // assign the cells to arrays by row and column
+    for (int i = 0; i < chip->cells->n; i++) {
+
+	pmCell *cell = chip->cells->data[i];
+
+	// skip the video cells and empty cells (cell->readouts->n != 1)
+	if (cell->readouts->n != 1) {
+	    psWarning ("Skipping Empty and Video Cell for ppImageCrosstalk");
+	    continue;
+	}
+
+	// place the image from this cell in the 2D array:
+	const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");	    
+	int row = cellName[3] - '0';
+	int col = cellName[2] - '0';
+	assert (row >= 0 && row < 8);
+	assert (col >= 0 && col < 8);
+
+	pmReadout *readout = cell->readouts->data[0];
+	psImage *image = readout->image;
+
+	psArray *cellRow = cellRows->data[row];
+	cellRow->data[col] = psMemIncrRefCounter(cell);
+
+	psArray *imageRow = imageRows->data[row];
+	imageRow->data[col] = psMemIncrRefCounter(image);
+    }
+
+    psVector *sample = NULL;
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS);
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN);
+
+    // measure the background for each cell
+    for (int row = 0; row < cellRows->n; row++) {
+	psArray *cellRow = cellRows->data[row];
+	psArray *imageRow = imageRows->data[row];
+	for (int col = 0; col < cellRow->n; col++) {
+	    pmCell *cell = cellRow->data[col];
+	    if (!cell) continue;
+	    psImage *image = imageRow->data[col];
+
+	    psStatsInit (stats);
+	    if (!psImageBackground (stats, &sample, image, NULL, 0, rng)) continue;
+	    psMetadataAddF32 (cell->analysis, PS_LIST_TAIL, "XTALK.REF", PS_META_REPLACE, "crosstalk measurement", stats->robustMedian);
+	    // fprintf (stderr, "xtalk.ref (xy%d%d) : %f\n", col, row, stats->robustMedian);
+	}
+    }
+
+    for (int row = 0; row < cellRows->n; row++) {
+	psArray *cellRow = cellRows->data[row];
+	psArray *imageRow = imageRows->data[row];
+	for (int col = 0; col < cellRow->n; col++) {
+	    psImage *image = imageRow->data[col];
+
+	    // initialize the storage vectors
+	    for (int i = 0; i < 8; i++) {
+		psVector *vector = vectorSet->data[i];
+		vector->n = 0;
+	    }
+
+	    int nBright = 0;
+
+	    // generate a vector for each of the other cells in this row
+	    // containing only the pixels for which this cell is > CUTOFF
+	    for (int iy = 0; iy < image->numRows; iy++) {
+		for (int ix = 0; ix < image->numCols; ix++) {
+
+		    if (image->data.F32[iy][ix] < CUTOFF) continue;
+		    nBright ++;
+
+		    // this is a pixel of interest in the target cell; extract the matched pixels
+			
+		    for (int i = 0; i < 8; i++) {
+			if (i == col) continue;
+
+			psImage *matchImage = imageRow->data[i];
+			if (!matchImage) continue;
+
+			psVector *vector = vectorSet->data[i];
+			psVectorAppend (vector, matchImage->data.F32[iy][ix]);
+		    }
+		}
+	    }
+	    // fprintf (stderr, "nBright for xy%d%d : %d\n", col, row, nBright);
+	    if (nBright < MIN_MATCHED_PIXELS) continue;
+
+	    // pmCell *refCell = cellRow->data[col];
+	    // float refBackground = psMetadataLookupF32 (&status, refCell->analysis, "XTALK.REF");
+	    // float swing = CUTOFF - refBackground;
+
+	    // now we need to measure the median for these vectors
+	    for (int i = 0; i < 8; i++) {
+		if (i == col) continue;
+		psVector *vector = vectorSet->data[i];
+		if (vector->n < MIN_MATCHED_PIXELS) continue;
+
+		pmCell *cell = cellRow->data[i];
+
+		psStatsInit (stats);
+		psVectorStats (stats, vector, NULL, NULL, 0);
+		    
+		float background = psMetadataLookupF32 (&status, cell->analysis, "XTALK.REF");
+		float xtalk = (stats->robustMedian - background) / CUTOFF;
+
+		// Put version information into the header
+		pmHDU *hdu = pmHDUGetHighest(fpa, chip, cell);
+		if (hdu) {
+		    // add this to the PHU header 
+		    snprintf (name, 64, "XT_%d%d_%d%d", col, row, i, row);
+		    psMetadataAddF32 (hdu->header, PS_LIST_TAIL, name, PS_META_REPLACE, "crosstalk measurement", xtalk);
+		}
+
+		// keyword for resulting value:
+		snprintf (name, 64, "XTALK_%d%d", i, row);
+
+		psMetadataAddF32 (cell->analysis, PS_LIST_TAIL, name, PS_META_REPLACE, "crosstalk measurement", xtalk);
+		// fprintf (stderr, "xtalk (xy%d%d on xy%d%d) : %f -> delta is %f, slope if %e\n", col, row, i, row, stats->robustMedian, stats->robustMedian - background, xtalk);
+	    }
+	}
+    }
+    psLogMsg ("ppImage", 3, "crosstalk measurement: %f sec\n", psTimerMark ("crosstalk"));
+
+    psFree (vectorSet);
+    psFree (cellRows);
+    psFree (imageRows);
+    psFree (stats);
+    psFree (rng);
+    psFree (sample);
+
+    // need to free all sorts of things here....
+    return true;
+}
+
Index: /trunk/ppImage/src/ppImageOptions.c
===================================================================
--- /trunk/ppImage/src/ppImageOptions.c	(revision 23844)
+++ /trunk/ppImage/src/ppImageOptions.c	(revision 23845)
@@ -61,4 +61,8 @@
     options->markValue       = 0x00;    // A safe bit for internal marking
 
+    // crosstalk options
+    options->doCrosstalkMeasure = false;   // measure crosstalk
+    options->doCrosstalkCorrect = false;   // correct crosstalk
+
     // Non-linearity default options
     options->nonLinearType   = 0;       // Type of non-linearity data (vector, string or metadata)
@@ -207,4 +211,8 @@
     options->doMask = psMetadataLookupBool(NULL, recipe, "MASK");
 
+    // Mask recipe options (note that mask bit values are set in ppImageSetMaskBits.c)
+    options->doCrosstalkMeasure = psMetadataLookupBool(NULL, recipe, "CROSSTALK.MEASURE");
+    options->doCrosstalkCorrect = psMetadataLookupBool(NULL, recipe, "CROSSTALK.CORRECT");
+
     options->doBias = psMetadataLookupBool(NULL, recipe, "BIAS");
     options->doDark = psMetadataLookupBool(NULL, recipe, "DARK");
