Index: /trunk/ppImage/src/ppImageLoop_Threaded.c
===================================================================
--- /trunk/ppImage/src/ppImageLoop_Threaded.c	(revision 18823)
+++ /trunk/ppImage/src/ppImageLoop_Threaded.c	(revision 18823)
@@ -0,0 +1,168 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PS_ERR_UNKNOWN, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool ppImageLoop (pmConfig *config, ppImageOptions *options) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // measure the total elapsed time in ppImageLoop.
+    psTimerStart ("ppImageLoop");
+
+    pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.INPUT");
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find input data!\n");
+        ppImageCleanup(config, options);
+        exit(PS_EXIT_PROG_ERROR);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0);// View for level of interest
+    pmHDU *lastHDU = NULL;              // Last HDU that was updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for FPA");
+
+    while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Chip");
+
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
+            psLogMsg ("ppImageLoop", 5, "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)) ESCAPE ("load failure for Cell");
+
+            // Put version information into the header
+            pmHDU *hdu = pmHDUFromCell(cell);
+            if (hdu && hdu != lastHDU) {
+                ppImageVersionMetadata(hdu->header);
+                lastHDU = hdu;
+            }
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Readout");
+                if (!readout->data_exists) {
+                    continue;
+                }
+
+		// XXX set the options->*Mask values here (after the mask images have been loaded
+		// and before any of the value are used)
+		if (!ppImageSetMaskBits (config, options)) ESCAPE ("Unable to set bit masks");
+
+		// allocate a job
+		// XXX we are threading by pmReadout, but this only helps mu
+		psThreadJob *job = psThreadJobAlloc ("PPIMAGE_DETREND_READOUT", 0);
+
+		// construct the arguments for this job
+		psArrayAdd (job->args, 1, config);
+		psArrayAdd (job->args, 1, options);
+		psArrayAdd (job->args, 1, view);
+
+		// ppImageDetrendReadout(config, options, view)
+		if (!psThreadJobAddPending (job)) ESCAPE ("Unable to detrend readout");
+            }
+
+	    // wait for the threads to finish and manage results
+	    if (nThreads) {
+		// wait here for the threaded jobs to finish
+		if (!psThreadPoolWait ()) {
+		    psError(PS_ERR_UNKNOWN, false, "Unable to interpolate image.");
+		    return false;
+		}
+		fprintf (stderr, "success for threaded jobs\n");
+
+		// we don't care about the results, just dump the done queue jobs
+		psThreadJob *job = NULL;
+		while ((job = psThreadJobGetDone()) != NULL) {
+		    psFree (job);
+		}
+	    }
+
+        }
+
+        // Apply the fringe correction
+        if (options->doFringe) {
+	    if (!ppImageDetrendFringeApply (config, chip, view, options)) 
+		ESCAPE ("Unable to defringe");
+	}
+	
+	// measure various statistics for this image
+	if (!ppImagePixelStats (config, options, view)) 
+	    ESCAPE ("Unable to measures stats for image");
+
+        if (!ppImageMosaicChip(config, options, view, "PPIMAGE.CHIP", "PPIMAGE.OUTPUT"))
+            ESCAPE ("Unable to mosaic chip");
+
+        // we perform photometry on the readouts of this chip in the output
+        if (options->doPhotom) {
+            if (!ppImagePhotom(config, view)) ESCAPE ("error running photometry.");
+        }
+
+	// replace the masked pixels with the background level
+	if (options->replaceMasked) {
+	    if (!ppImageReplaceBackground (config, view, options)) 
+		ESCAPE ("Unable to replace masked pixels with background level");
+	}
+
+	// binning (used for display) must take place after the background is replaced, if desired
+        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN1"))
+            ESCAPE ("Unable to bin chip (level 1).");
+
+        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN2"))
+            ESCAPE ("Unable to bin chip (level 2).");
+
+        // Close cells (XXX shouldn't pmFPAfileClose iterate down as needed?)
+        view->cell = -1;
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Cell");
+        }
+
+        // Close chip
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Chip");
+    }
+
+    // generate the full-scale FPA mosaic
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA1", "PPIMAGE.BIN1")) ESCAPE ("failure in FPA Mosaic (level 1)");
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA2", "PPIMAGE.BIN2")) ESCAPE ("failure in FPA Mosaic (level 2)");
+
+    // we perform astrometry on all chips after sources have been detected
+    // this also performs the psastro file IO
+    if (options->doAstromChip || options->doAstromMosaic) {
+        if (!ppImageAstrom(config)) ESCAPE ("error running astrometry.");
+    }
+
+    if (psTraceGetLevel("ppImage") >= 3) {
+	ppImageFileCheck (config);
+    }
+
+    // Write out summary statistics
+    if (!ppImageMetadataStats (config, options)) ESCAPE ("Unable to write statistics file.");
+
+    // Write out summary statistics
+    if (!ppImageStatsOutput (config, options)) ESCAPE ("Unable to write statistics file.");
+
+    // Output and Close FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for FPA");
+
+    psFree(view);
+    return true;
+}
Index: /trunk/ppImage/src/ppImageSetThreads.c
===================================================================
--- /trunk/ppImage/src/ppImageSetThreads.c	(revision 18823)
+++ /trunk/ppImage/src/ppImageSetThreads.c	(revision 18823)
@@ -0,0 +1,26 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageThread_ppImageDetrendReadout (psThreadJob *job) {
+
+    pmConfig *config        = job->args->data[0];
+    ppImageOptions *options = job->args->data[1];
+    pmFPAview *view         = job->args->data[2];
+
+    bool status = ppImageDetrendReadout(config, options, view);
+    return status;
+}
+
+bool ppImageSetThreads () {
+
+    psThreadTask *task = NULL;
+
+    task = psThreadTaskAlloc ("PPIMAGE_DETREND_READOUT", 3);
+    task->function = &ppImageThread_ppImageDetrendReadout;
+    psThreadTaskAdd (task);
+
+    return true;
+}
