Index: /trunk/psastro/src/.cvsignore
===================================================================
--- /trunk/psastro/src/.cvsignore	(revision 15879)
+++ /trunk/psastro/src/.cvsignore	(revision 15880)
@@ -13,2 +13,3 @@
 config.h.in
 stamp-h1
+psastroModel
Index: /trunk/psastro/src/Makefile.am
===================================================================
--- /trunk/psastro/src/Makefile.am	(revision 15879)
+++ /trunk/psastro/src/Makefile.am	(revision 15880)
@@ -3,8 +3,13 @@
 libpsastro_la_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSASTRO_CFLAGS)
 
-bin_PROGRAMS = psastro
+bin_PROGRAMS = psastro psastroModel
+
 psastro_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSASTRO_CFLAGS)
 psastro_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSASTRO_LIBS)
 psastro_LDADD = libpsastro.la
+
+psastroModel_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSASTRO_CFLAGS)
+psastroModel_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSASTRO_LIBS)
+psastroModel_LDADD = libpsastro.la
 
 psastro_SOURCES = \
@@ -13,4 +18,13 @@
 	psastroParseCamera.c   	    \
 	psastroDataLoad.c           \
+	psastroDataSave.c           \
+	psastroCleanup.c
+
+psastroModel_SOURCES = \
+	psastroModel.c		    \
+        psastroModelArguments.c	    \
+	psastroModelParseCamera.c   \
+	psastroModelDataLoad.c      \
+	psastroModelAnalysis.c      \
 	psastroCleanup.c
 
@@ -20,5 +34,4 @@
 	psastroErrorCodes.c         \
 	psastroVersion.c            \
-	psastroDataSave.c           \
 	psastroDefineFiles.c        \
 	psastroAnalysis.c           \
Index: /trunk/psastro/src/psastroModel.c
===================================================================
--- /trunk/psastro/src/psastroModel.c	(revision 15880)
+++ /trunk/psastro/src/psastroModel.c	(revision 15880)
@@ -0,0 +1,41 @@
+# include "psastroStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    pmConfig *config = NULL;
+
+    psTimerStart ("complete");
+
+    psastroErrorRegister();              // register our error codes/messages
+
+    // model inits are needed in pmSourceIO
+    // models defined in psphot/src/models are not available in psastro
+    pmModelClassInit ();
+
+    // load configuration information
+    config = psastroModelArguments (argc, argv);
+
+    // load identify the data sources
+    if (!psastroModelParseCamera (config)) {
+	psErrorStackPrint(stderr, "error setting up the camera\n");
+	exit (1);
+    }
+
+    // load the raw pixel data (from PSPHOT.SOURCES)
+    // select subset of stars for astrometry
+    if (!psastroModelDataLoad (config)) {
+	psErrorStackPrint(stderr, "error loading input data\n");
+	exit (1);
+    }
+
+    // run the full astrometry analysis (chip and/or mosaic)
+    if (!psastroModelAnalysis (config)) {
+	psErrorStackPrint(stderr, "failure in psastro analysis\n");
+	exit (1);
+    }
+    
+    psLogMsg ("psastro", 3, "complete psastro run: %f sec\n", psTimerMark ("complete"));
+
+    // psastroCleanup (config);
+    exit (EXIT_SUCCESS);
+}
Index: /trunk/psastro/src/psastroModelAnalysis.c
===================================================================
--- /trunk/psastro/src/psastroModelAnalysis.c	(revision 15880)
+++ /trunk/psastro/src/psastroModelAnalysis.c	(revision 15880)
@@ -0,0 +1,89 @@
+# include "psastroStandAlone.h"
+
+bool psastroModelAnalysis (pmConfig *config) {
+
+    bool status;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+	return false;
+    }
+
+    // physical pixel scale in microns per pixel
+    char *refChip = psMetadataLookupStr (&status, recipe, "PSASTRO.MODEL.REF.CHIP");
+    if (!refChip) {
+	psError(PS_ERR_IO, true, "reference chip is missing from recipe"); 
+	return false; 
+    } 
+
+    /* model analysis steps:
+
+    1) determine POS_ZERO:
+    * choose a reference chip
+    * what is posangle of chip
+    * posangle = FPA.POSANGLE - POS_ZERO
+    * POS_ZERO = FPA.POSANGLE - posangle
+
+    ** average over input images
+
+    2) determine boresite model
+
+    * find CRPIX1,2 for a series of images
+    * fit ellipse 
+
+    */
+
+    /***** find the POSANGLE offset *****/
+
+    // select the input pmFPAfile pointers
+    psMetadataItem *item = psMetadataLookup (config->files, "PSASTRO.WCS");
+    if (item == NULL) psAbort("missing PSASTRO.WCS entries in config->files");
+    if (item->type != PS_DATA_METADATA_MULTI) psAbort("unexpected type for PSASTRO.WCS");
+    psArray *files = psListToArray (item->data.list);
+
+    // temp data vectors
+    psVector *posZero = psVectorAlloc (files->n, PS_TYPE_F32);
+    psVector *Lo      = psVectorAlloc (files->n, PS_TYPE_F32);
+    psVector *Mo      = psVectorAlloc (files->n, PS_TYPE_F32);
+
+    int n = 0;
+
+    // convert the headers for the input file into fpa astrometry terms
+    for (int i = 0; i < files->n; i++) {
+	psMetadataItem *file = files->data[i];
+	pmFPAfile *input = file->data.V;
+
+	double POSANGLE = psMetadataLookupF64 (&status, input->fpa->concepts, "FPA.POSANGLE"); 
+	if (!status) psAbort ("missing FPA.POSANGLE");
+
+    	// get chip from name
+	pmChip *chip = pmConceptsChipFromName (input->fpa, refChip);
+	if (!chip) psAbort ("invalid chip name for reference");
+
+	if (!chip->toFPA) continue;
+
+	// we have two measurements of the posangle (may be parity flipped to different quadrants)
+	float posangle = PM_DEG_RAD * atan2 (chip->toFPA->y->coeff[1][0], chip->toFPA->x->coeff[1][0]);
+	posZero->data.F32[n] = POSANGLE - posangle;
+	while (posZero->data.F32[n] > 360.0) posZero->data.F32[n] -= 360.0;
+	while (posZero->data.F32[n] <   0.0) posZero->data.F32[n] += 360.0;
+
+	Lo->data.F32[n] = chip->toFPA->x->coeff[0][0];
+	Mo->data.F32[n] = chip->toFPA->y->coeff[0][0];
+	fprintf (stderr, "%d : %f %f : %f = %f - %f\n", i, Lo->data.F32[n], Mo->data.F32[n], posZero->data.F32[n], POSANGLE, posangle);
+	n ++;
+    }
+      
+    Lo->n = n;
+    Mo->n = n;
+    posZero->n = n;
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    psVectorStats (stats, posZero, NULL, NULL, 0);
+
+    fprintf (stderr, "pos zero %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+    return true;
+}
Index: /trunk/psastro/src/psastroModelArguments.c
===================================================================
--- /trunk/psastro/src/psastroModelArguments.c	(revision 15880)
+++ /trunk/psastro/src/psastroModelArguments.c	(revision 15880)
@@ -0,0 +1,63 @@
+# include "psastroStandAlone.h"
+
+static char *usage = "USAGE: psastroModel [-output root] (cmffiles)";
+
+pmConfig *psastroModelArguments (int argc, char **argv) {
+
+    int N;
+
+    if (argc == 1) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "No arguments supplied");
+        psErrorStackPrint(stderr, usage);
+	exit (1);
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSASTRO_RECIPE);
+    if (config == NULL) {
+        psError(PSASTRO_ERR_CONFIG, false, "Can't read site configuration");
+        psErrorStackPrint(stderr, usage);
+	exit (1);
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSASTRO recipe values loaded from recipe files
+
+    // XXX no options yet defined
+    // psMetadata *options = pmConfigRecipeOptions (config, PSASTRO_RECIPE);
+
+    // define the output filename
+    if ((N = psArgumentGet (argc, argv, "-output"))) {
+        psArgumentRemove (N, &argc, argv);
+	psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    } else {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "Missing -output (root)");
+        psErrorStackPrint(stderr, usage);
+	exit (1);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+    
+    if (argc < 1) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "Incorrect arguments supplied");
+        psErrorStackPrint(stderr, "exit");
+	exit (1);
+    }
+
+    // each additional word is a file; create names INPUT.%d for them
+    for (int i = 0; i < argc - 1; i++) {
+	char name[16];
+	snprintf (name, 16, "INPUT.%d", i);
+	psArray *array = psArrayAlloc(1);
+	array->data[0] = psStringCopy (argv[i+1]);
+	psMetadataAddPtr(config->arguments, PS_LIST_TAIL, name,  PS_DATA_ARRAY, "", array);
+    }	
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INPUT.N", 0, "", argc - 1);
+    return (config);
+}
Index: /trunk/psastro/src/psastroModelDataLoad.c
===================================================================
--- /trunk/psastro/src/psastroModelDataLoad.c	(revision 15880)
+++ /trunk/psastro/src/psastroModelDataLoad.c	(revision 15880)
@@ -0,0 +1,100 @@
+# include "psastroStandAlone.h"
+
+// this loop loads the header data from the input files, using the output pmFPAfile to guide
+// the chip selection and related issues
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "Failure in psastroModelDataLoad"); \
+  psFree (view); \
+  return false; \
+}
+  
+// all of the different astrometry analysis modes use the same data load loop
+bool psastroModelDataLoad (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+
+    psTimerStart ("psastro");
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    // physical pixel scale in microns per pixel
+    double pixelScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.SCALE");
+    if (!status) {
+	psError(PS_ERR_IO, true, "Failed to lookup pixel scale"); 
+	return false; 
+    } 
+
+    // select the input data sources
+    pmFPAfile *output = psMetadataLookupPtr (NULL, config->files, "PSASTRO.MODEL");
+    if (!output) psAbort ("PSASTRO.MODEL not listed in config->files");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    while ((chip = pmFPAviewNextChip (view, output->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process) { continue; }
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+    }
+    psLogMsg ("psastro", 3, "load headers : %f sec\n", psTimerMark ("psastro"));
+
+    // we should have a number of different files stored in config->files as PSASTRO.WCS
+    psMetadataItem *item = psMetadataLookup (config->files, "PSASTRO.WCS");
+    if (item == NULL) psAbort("missing PSASTRO.WCS entries in config->files");
+    if (item->type != PS_DATA_METADATA_MULTI) psAbort("unexpected type for PSASTRO.WCS");
+    psArray *files = psListToArray (item->data.list);
+
+    // convert the headers for the input file into fpa astrometry terms
+    for (int i = 0; i < files->n; i++) {
+	psMetadataItem *file = files->data[i];
+	pmFPAfile *input = file->data.V;
+
+	pmFPAviewReset (view);
+
+	// check PHU header to see if we are using mosaic-level or per-chip astrometry
+	bool bilevelAstrometry = false;
+	pmHDU *phu = pmFPAviewThisPHU (view, input->fpa);
+	if (phu) {
+	    char *ctype = psMetadataLookupStr (NULL, phu->header, "CTYPE1");
+	    if (ctype) bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+	}
+	if (bilevelAstrometry) {
+	    pmAstromReadBilevelMosaic (input->fpa, phu->header);
+	} 
+
+	while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+	    psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	    if (!chip->process || !chip->file_exists || !chip->data_exists) { continue; }
+
+	    // read WCS data from the corresponding header
+	    pmHDU *hdu = pmFPAviewThisHDU (view, input->fpa);
+	    int nAstro = psMetadataLookupS32 (&status, hdu->header, "NASTRO");
+	    if (!nAstro) continue;
+
+	    if (bilevelAstrometry) {
+		if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+		    psWarning("Could not get WCS information from header for chip %d, skipping", view->chip); 
+		    continue;
+		} 
+	    } else {
+		if (!pmAstromReadWCS (input->fpa, chip, hdu->header, pixelScale)) {
+		    psWarning("Could not get WCS information from header for chip %d, skipping", view->chip); 
+		    continue;
+		} 
+	    }
+	}
+    }
+    psLogMsg ("psastro", 3, "convert wcs terms to internal format : %f sec\n", psTimerMark ("psastro"));
+
+    psFree (view);
+    return true;
+}
Index: /trunk/psastro/src/psastroModelParseCamera.c
===================================================================
--- /trunk/psastro/src/psastroModelParseCamera.c	(revision 15880)
+++ /trunk/psastro/src/psastroModelParseCamera.c	(revision 15880)
@@ -0,0 +1,44 @@
+# include "psastroInternal.h"
+
+bool psastroModelParseCamera (pmConfig *config) {
+
+    bool status = false;
+    pmFPAfile *input = NULL;
+
+    int nInput = psMetadataLookupS32 (&status, config->arguments, "INPUT.N");
+    if (!status) psAbort ("missing INPUT.N in config->arguments");
+
+    // find and load the input files (this determines the camera and builds the fpa structures,
+    // but does not load the data)
+    for (int i = 0; i < nInput; i++) {
+	char name[16];
+	snprintf (name, 16, "INPUT.%d", i);
+	input = pmFPAfileDefineFromArgs (&status, config, "PSASTRO.WCS", name);
+    }
+	
+    // set up an output fpa structure & file based on the last input file
+    pmFPAfile *output = pmFPAfileDefineOutput (config, input->fpa, "PSASTRO.MODEL");
+    if (!output) {
+	psError(PSASTRO_ERR_CONFIG, false, "Failed to build FPA for PSASTRO.MODEL from input");
+	return false;
+    }
+    output->save = true;
+
+    // Chip selection: turn on only the chips specified (option is not required)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS"); 
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	pmFPASelectChip (output->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(output->fpa, chipNum, false)) {
+		psError(PSASTRO_ERR_CONFIG, true, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+
+    psTrace("psastro", 1, "Done with psastroModelParseCamera...\n");
+    return true;
+}
Index: /trunk/psastro/src/psastroStandAlone.h
===================================================================
--- /trunk/psastro/src/psastroStandAlone.h	(revision 15879)
+++ /trunk/psastro/src/psastroStandAlone.h	(revision 15880)
@@ -17,3 +17,8 @@
 bool              psastroDataLoad (pmConfig *config);
 
+pmConfig *psastroModelArguments (int argc, char **argv);
+bool psastroModelParseCamera (pmConfig *config);
+bool psastroModelDataLoad (pmConfig *config);
+bool psastroModelAnalysis (pmConfig *config);
+
 #endif
