Index: /trunk/psphot/src/psphotDetect.c
===================================================================
--- /trunk/psphot/src/psphotDetect.c	(revision 21353)
+++ /trunk/psphot/src/psphotDetect.c	(revision 21353)
@@ -0,0 +1,41 @@
+# include "psphotDetect.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: psphotDetect [-mask image(s)] [-masklist masklist] [-psf psfmodel] [-points pointlist] (output)\n");
+    exit (PS_EXIT_CONFIG_ERROR);
+}
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();			// register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotDetectArguments (argc, argv);
+    if (!config) {
+	psErrorStackPrint(stderr, "Error reading arguments\n");
+	usage ();
+    }
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotDetectParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotDetectImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphotDetect image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphotDetect run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /trunk/psphot/src/psphotDetect.h
===================================================================
--- /trunk/psphot/src/psphotDetect.h	(revision 21353)
+++ /trunk/psphot/src/psphotDetect.h	(revision 21353)
@@ -0,0 +1,21 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#ifndef PSPHOT_DETECT_H
+#define PSPHOT_DETECT_H
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+// Top level functions
+pmConfig       *psphotArguments (int argc, char **argv);
+bool            psphotParseCamera (pmConfig *config);
+bool            psphotImageLoop (pmConfig *config);
+bool            psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile);
+void            psphotCleanup (pmConfig *config);
+psExit          psphotGetExitStatus ();
+
+#endif
Index: /trunk/psphot/src/psphotDetectArguments.c
===================================================================
--- /trunk/psphot/src/psphotDetectArguments.c	(revision 21353)
+++ /trunk/psphot/src/psphotDetectArguments.c	(revision 21353)
@@ -0,0 +1,57 @@
+# include "psphotDetect.h"
+
+pmConfig *psphotDetectArguments(int argc, char **argv) {
+
+    int N;
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-version"))) {
+        psString version;
+        version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+        psError(PSPHOT_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // XXX add psphot thread arguments?
+
+    // all three of these input files are required: if not found, we will exit
+    if (!pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",   "-mask",    "-masklist")) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "mask image not supplied");
+        return NULL;
+    }
+    if (!pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf", "-psflist")) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "mask image not supplied");
+        return NULL;
+    }
+    if (!pmConfigFileSetsMD (config->arguments, &argc, argv, "POINTS", "-points",  "-pointslist")) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "mask image not supplied");
+        return NULL;
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+        return NULL;
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotDetectArguments...\n");
+    return (config);
+}
Index: /trunk/psphot/src/psphotDetectImageLoop.c
===================================================================
--- /trunk/psphot/src/psphotDetectImageLoop.c	(revision 21353)
+++ /trunk/psphot/src/psphotDetectImageLoop.c	(revision 21353)
@@ -0,0 +1,97 @@
+# include "psphotDetect.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotDetectImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *mask = psMetadataLookupPtr (&status, config->files, "PSPHOT.MASK");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "Can't find input mask data!");
+	return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // for psphot, we force data to be read at the chip level 
+    while ((chip = pmFPAviewNextChip (view, mask->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+	// load just the input image data (image, mask, weight)
+	pmFPAfileActivate (config->files, false, NULL);
+	pmFPAfileActivate (config->files, true, "PSPHOT.MASK");
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+	// mosaic the cells of a chip into a single contiguous (trimmed) chip
+        if (!psphotMosaicChip(config, view, "PSPHOT.INPUT", "PSPHOT.MASK")) ESCAPE ("Unable to mosaic chip.");
+
+	// try to load other supporting data (PSF, SRC, etc).
+	// do not re-load the mask three files
+	pmFPAfileActivate (config->files, true, NULL);
+	pmFPAfileActivate (config->files, false, "PSPHOT.MASK");
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+	// re-activate files so they will be closed and freed below
+	pmFPAfileActivate (config->files, true, NULL);
+
+	// there is now only a single chip (multiple readouts?). loop over it and process
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		psLogMsg ("psphot", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+		if (!readout->data_exists) { continue; }
+
+		// run the actual photometry analysis on this chip/cell/readout
+		if (!psphotDetectReadout (config, view)) {
+		    psError(psErrorCodeLast(), false, "failure in psphotDetectReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+	    }
+	}
+
+	// save output which is saved at the chip level
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed output for Chip in psphot.");
+    }
+    // save output which is saved at the fpa level
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed ouput for FPA in psphot.");
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
+// PSPHOT.MASK
+// PSPHOT.WEIGHT
+// 
Index: /trunk/psphot/src/psphotDetectParseCamera.c
===================================================================
--- /trunk/psphot/src/psphotDetectParseCamera.c	(revision 21353)
+++ /trunk/psphot/src/psphotDetectParseCamera.c	(revision 21353)
@@ -0,0 +1,43 @@
+# include "psphotDetect.h"
+
+// define the needed / desired I/O files
+bool psphotDetectParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // if MASK or WEIGHT was supplied on command line, bind files to 'load'
+    // the mask and weight will be mosaicked with the image
+    pmFPAfile *mask = pmFPAfileDefineFromArgs (&status, config, "PSPHOT.MASK", "MASK");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (!psphotSetMaskBits (config)) {
+      // XXX shouldn't this come after the file has been read?
+        psError (PS_ERR_UNKNOWN, false, "failed to set mask bit values");
+        return NULL;
+    }
+    mask->dataLevel = PM_FPA_LEVEL_CHIP; // force load at the CHIP level
+
+    // the psphot analysis is performed on chips
+    pmFPAfile *input = pmFPAfileDefineChipMosaic(config, mask->fpa, "PSPHOT.INPUT");
+    if (!input) {
+        psError(PSPHOT_ERR_CONFIG, false, _("Unable to generate new file from PSPHOT.INPUT"));
+        return NULL;
+    }
+
+    // the PSF model is required
+    pmFPAfileBindFromArgs(&status, mask, config, "PSPHOT.PSF.LOAD", "PSPHOT.PSF");
+    if (!status) {
+      psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.PSF.LOAD");
+      return status;
+    }
+
+    if (!pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT.PTS", "POINTS")) {
+      psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.INPUT.PTS");
+      return status;
+    }
+
+    psErrorClear();                     // some metadata lookup may have failed
+    return true;
+}
Index: /trunk/psphot/src/psphotDetectReadout.c
===================================================================
--- /trunk/psphot/src/psphotDetectReadout.c	(revision 21353)
+++ /trunk/psphot/src/psphotDetectReadout.c	(revision 21353)
@@ -0,0 +1,65 @@
+# include "psphotInternal.h"
+
+bool psphotDetectReadout(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
+    // for psphot envisions threading within psphotReadout, not multiple threads calling
+    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
+    // jointly by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, false);
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndWeight (config, readout, recipe);
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup(config, readout, recipe, NULL, NULL, NULL);
+    }
+
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved in the recipe
+    pmPSF *psf = psphotLoadPSF (config, view, recipe);
+    psAssert (psf, "psf should be loaded");
+
+
+    // grab the sources of interest from the storage location (pmFPAfile PSPHOT.INPUT.CMF)
+    psArray *sources = psphotLoadPSFSources (config, view);
+    if (!sources) {
+      psError(PS_ERR_UNKNOWN, false, "No sources supplied to measure PSF");
+      return false;
+    }
+
+    // include externally-supplied sources (supplied as PSPHOT.INPUT.CMF)
+    pmDetections *detections = psphotDetectionsFromSources (config, inSources);
+    if (!detections || !detections->peaks) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Can't find PSF stars");
+        return psphotReadoutCleanup(config, readout, recipe, detections, NULL, NULL);
+    }
+
+    // construct sources and measure basic stats
+    psArray *sources = psphotSourceStats (config, readout, detections);
+    if (!sources) return false;
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // classify sources based on moments, brightness
+    if (!psphotRoughClass (readout, sources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // calculate source magnitudes
+    psphotPSFWeights(config, readout, view, sources, psf);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+}
