Index: trunk/Ohana/src/dvopsps/src/insert_detections_dvopsps_catalog.c
===================================================================
--- trunk/Ohana/src/dvopsps/src/insert_detections_dvopsps_catalog.c	(revision 36374)
+++ trunk/Ohana/src/dvopsps/src/insert_detections_dvopsps_catalog.c	(revision 36375)
@@ -202,5 +202,5 @@
 
   // PrintIOBuffer (buffer, "INSERT INTO dvoDetectionFull (imageID, ippDetectID, detectID, ippObjID, objID, photcode, flags, zp, zpErr, airMass, expTime, ra, dec_, raErr, decErr) VALUES \n");
-  PrintIOBuffer (buffer, "INSERT IGNORE INTO dvoDetectionFull (imageID, ippDetectID, detectID, ippObjID, objID, flags, zp, zpErr, airMass, expTime, ra, dec_, raErr, decErr) VALUES \n");
+  PrintIOBuffer (buffer, "INSERT INTO dvoDetectionFull (imageID, ippDetectID, detectID, ippObjID, objID, flags, zp, zpErr, airMass, expTime, ra, dec_, raErr, decErr) VALUES \n");
 
   return TRUE;
Index: trunk/Ohana/src/opihi/cmd.astro/fitplx.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.astro/fitplx.c	(revision 36374)
+++ trunk/Ohana/src/opihi/cmd.astro/fitplx.c	(revision 36375)
@@ -41,4 +41,8 @@
     remove_argument (N, &argc, argv);
     VERBOSE = TRUE;
+  }
+  if ((N = get_argument (argc, argv, "-vv"))) {
+    remove_argument (N, &argc, argv);
+    VERBOSE = 2;
   }
 
@@ -145,5 +149,7 @@
 
   PlxFit fit;
-  FitPMandPar (&fit, X, dX, Y, dY, t, pX, pY, n, VERBOSE);
+  if (!FitPMandPar (&fit, X, dX, Y, dY, t, pX, pY, n, VERBOSE)) {
+    return FALSE;
+  }
 
   // fprintf (stderr, "Roff, Doff: %f, %f; dRo, dDo: %f, %f\n", fit.Ro, fit.Do, fit.dRo, fit.dDo);
@@ -204,9 +210,9 @@
   for (i = 0; i < Npts; i++) {
 
-    if (VERBOSE) fprintf (stderr, "%f %f : %f %f : %f : %f %f\n", X[i], dX[i], Y[i], dY[i], T[i], pR[i], pD[i]);
+    if (VERBOSE == 2) fprintf (stderr, "%f %f : %f %f : %f : %f %f\n", X[i], dX[i], Y[i], dY[i], T[i], pR[i], pD[i]);
 
     /* handle case where dX or dY = 0.0 */
-    wx = 1.0 / SQ(dX[i]);
-    wy = 1.0 / SQ(dY[i]);
+    wx = (fabs(dX[i]) < 0.0001) ? 1.0 : 1.0 / SQ(dX[i]);
+    wy = (fabs(dY[i]) < 0.0001) ? 1.0 : 1.0 / SQ(dY[i]);
 
     Wx += wx;
@@ -266,5 +272,17 @@
   B[4][0] = PRX + PDY;
 
-  dgaussjordan ((double **)A, (double **)B, 5, 1);
+  if (!dgaussjordan ((double **)A, (double **)B, 5, 1)) {
+    if (VERBOSE) fprintf (stderr, "error in fit\n");
+    if (VERBOSE == 2) {
+      int j;
+      for (i = 0; i < 5; i++) {
+	for (j = 0; j < 5; j++) {
+	  fprintf (stderr, "%e ", A[i][j]);
+	}
+	fprintf (stderr, " : %e\n", A[i][0]);
+      }
+    }
+    return FALSE;
+  }
 
   fit[0].Ro = B[0][0];
@@ -285,7 +303,9 @@
     Xf = fit[0].Ro + fit[0].uR*T[i] + fit[0].p*pR[i];
     Yf = fit[0].Do + fit[0].uD*T[i] + fit[0].p*pD[i];
-    chisq += SQ(X[i] - Xf) / SQ(dX[i]);
-    chisq += SQ(Y[i] - Yf) / SQ(dY[i]);
-    if (VERBOSE) fprintf (stderr, "chisq contrib : %f %f : %f %f : %f %f : %f %f : %f\n", Xf, Yf, X[i] - Xf, Y[i] - Yf, dX[i], dY[i], (X[i] - Xf) / dX[i], (Y[i] - Yf) / dY[i], chisq);
+    wx = (fabs(dX[i]) < 0.0001) ? 1.0 : 1.0 / SQ(dX[i]);
+    wy = (fabs(dY[i]) < 0.0001) ? 1.0 : 1.0 / SQ(dY[i]);
+    chisq += SQ(X[i] - Xf) * wx;
+    chisq += SQ(Y[i] - Yf) * wy;
+    // if (VERBOSE) fprintf (stderr, "chisq contrib : %f %f : %f %f : %f %f : %f %f : %f\n", Xf, Yf, X[i] - Xf, Y[i] - Yf, dX[i], dY[i], (X[i] - Xf) / dX[i], (Y[i] - Yf) / dY[i], chisq);
   }
   fit[0].Nfit = Npts;
Index: trunk/Ohana/src/opihi/cmd.data/Makefile
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 36374)
+++ trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 36375)
@@ -64,4 +64,6 @@
 $(SRC)/imhist.$(ARCH).o	\
 $(SRC)/imsmooth.$(ARCH).o	\
+$(SRC)/imsmooth.generic.$(ARCH).o	\
+$(SRC)/imsmooth.2d.$(ARCH).o	\
 $(SRC)/integrate.$(ARCH).o	\
 $(SRC)/interpolate.$(ARCH).o	\
Index: trunk/Ohana/src/opihi/cmd.data/imsmooth.2d.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/imsmooth.2d.c	(revision 36375)
+++ trunk/Ohana/src/opihi/cmd.data/imsmooth.2d.c	(revision 36375)
@@ -0,0 +1,214 @@
+# include "data.h"
+
+# define NRAD_MAX 44
+static float radii2[NRAD_MAX] = {  0.0,  1.0,  2.0,  4.0,  5.0, 
+				   8.0,  9.0, 10.0, 13.0, 16.0, 
+				   17.0, 18.0, 20.0, 25.0, 26.0, 
+				   29.0, 32.0, 34.0, 36.0, 37.0, 
+				   40.0, 41.0, 45.0, 49.0, 50.0, 
+				   52.0, 53.0, 58.0, 61.0, 64.0,
+				   65.0, 68.0, 72.0, 73.0, 74.0, 
+				   80.0, 81.0, 82.0, 85.0, 89.0,
+				   90.0, 97.0, 98.0, 100.0
+};
+
+static int   radiiN[NRAD_MAX] = {    1,  4, 4,  4,  8,
+				     4,  4, 8,  8,  4,
+				     8,  4, 8, 12,  8, 
+				     8,  4, 8,  4,  8,
+				     8,  8, 8,  4, 12,
+				     8,  8, 8,  8,  4, 
+				     16, 8, 4,  8,  8, 
+				      8, 4, 8, 16,  8, 
+				      8, 8, 4, 12
+};
+
+/*
+ 0,  0 :  0 : 0  :   0
+ 0,  1 :  1 : 1  :   1
+ 1,  1 :  2 : 1  :   2
+ 0,  2 :  3 : 2  :   4
+ 1,  2 :  4 : 2  :   5
+ 2,  2 :  5 : 2  :   8
+ 0,  3 :  6 : 3  :   9
+ 1,  3 :  7 : 3  :  10
+ 2,  3 :  8 : 3  :  13
+ 0,  4 :  9 : 4  :  16
+ 1,  4 : 10 : 4  :  17
+ 3,  3 : 11 : 4  :  18
+ 2,  4 : 12 : 4  :  20
+ 0,  5 : 13 : 5  :  25
+ 3,  4 : 13 : 5  :  25
+ 1,  5 : 14 : 5  :  26
+ 2,  5 : 15 : 5  :  29
+ 4,  4 : 16 : 5  :  32
+ 3,  5 : 17 : 5  :  34
+ 0,  6 : 18 : 6  :  36
+ 1,  6 : 19 : 6  :  37
+ 2,  6 : 20 : 6  :  40
+ 4,  5 : 21 : 6  :  41
+ 3,  6 : 22 : 6  :  45
+ 0,  7 : 23 : 7  :  49
+ 1,  7 : 24 : 7  :  50 *
+ 5,  5 : 24 : 7  :  50 *
+ 4,  6 : 25 : 7  :  52
+ 2,  7 : 26 : 7  :  53
+ 3,  7 : 27 : 7  :  58
+ 5,  6 : 28 : 7  :  61
+ 0,  8 : 29 : 8  :  64
+ 1,  8 : 30 : 8  :  65 *
+ 4,  7 : 30 : 8  :  65 *
+ 2,  8 : 31 : 8  :  68
+ 6,  6 : 32 : 8  :  72
+ 3,  8 : 33 : 8  :  73
+ 5,  7 : 34 : 8  :  74
+ 4,  8 : 35 : 8  :  80
+ 0,  9 : 36 : 9  :  81
+ 1,  9 : 37 : 9  :  82
+ 2,  9 : 38 : 9  :  85 *
+ 6,  7 : 38 : 9  :  85 *
+ 5,  8 : 39 : 9  :  89
+ 3,  9 : 40 : 9  :  90
+ 4,  9 : 41 : 9  :  97
+ 7,  7 : 42 : 9  :  98
+ 0, 10 : 43 : 10 : 100 *
+ 6,  8 : 43 : 10 : 100 *
+ */
+
+# define ADD_AXIS(RAD,DD) s += radflux[RAD]*(vi[p - DD] + vi[p + DD] + vi[p - DD*Nx] + vi[p + DD*Nx]);
+# define ADD_DIAG(RAD,DD) s += radflux[RAD]*(vi[p - DD - DD*Nx] + vi[p + DD - DD*Nx] + vi[p - DD + DD*Nx] + vi[p + DD + DD*Nx]);
+# define ADD_RAND(RAD,DX,DY) s += radflux[RAD]*(vi[p - DX - DY*Nx] + vi[p + DX - DY*Nx] + vi[p - DX + DY*Nx] + vi[p + DX + DY*Nx] + \
+						vi[p - DY - DX*Nx] + vi[p + DY - DX*Nx] + vi[p - DY + DX*Nx] + vi[p + DY + DX*Nx]);
+
+int imsmooth_2d (int argc, char **argv) {
+  
+  int i, j;
+  float *vi, *vo;
+  Buffer *in;
+
+  if (argc != 4) {
+    gprint (GP_ERR, "USAGE: imsmooth (input) sigma Nsigma\n");
+    return (FALSE);
+  }
+  
+  if ((in  = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) return (FALSE);
+  float  sigma = atof(argv[2]);
+  float Nsigma = atof(argv[3]);
+
+  int Ns = (int)(Nsigma * sigma);
+  Ns = MAX (3, MIN (Ns, 10));
+  int Ns2 = Ns * Ns;
+
+  // we are going to use a hard-wired radial profile
+  float *radflux = NULL;
+  ALLOCATE (radflux, float, NRAD_MAX);
+  float sum = 0.0;
+  for (i = 0; i < NRAD_MAX; i++) {
+    float z = radii2[i] / SQ(sigma);
+    radflux[i] = 1.0 / (1.0 + z + pow(z,1.666));
+    if (radii2[i] > Ns2) continue;
+    sum += radiiN[i] * radflux[i];
+  }
+  for (i = 0; i < NRAD_MAX; i++) {
+    radflux[i] = radflux[i] / sum;
+  }
+
+  int Nx = in[0].matrix.Naxis[0];
+  int Ny = in[0].matrix.Naxis[1];
+  ALLOCATE (vo, float, Nx*Ny);
+  memset (vo, 0, Nx*Ny*sizeof(float));
+
+  /* smooth in X direction */
+  vi = (float *) in[0].matrix.buffer;
+  for (j = Ns; j < Ny - Ns; j++) {
+    for (i = Ns; i < Nx - Ns; i++) {
+
+      int p = i + j*Nx; // current pixel of interest
+      float s = radflux[0]*(vi[p]);
+
+      // r <= 1.0
+      ADD_AXIS (1,  1);      // r^2 = 1
+
+      // r <= 2.0
+      ADD_DIAG (2,  1);      // r^2 = 2
+      ADD_AXIS (3,  2);      // r^2 = 4
+
+      // r <= 3.0
+      ADD_RAND (4,  1,  2);  // r^2 = 5
+      ADD_DIAG (5,  2);      // r^2 = 8
+      ADD_AXIS (6,  3);      // r^2 = 9
+      if (Ns <= 3) goto finish;
+
+      // r <= 4.0
+      ADD_RAND (7,  1,  3);  // r^2 = 10
+      ADD_RAND (8,  2,  3);  // r^2 = 13
+      ADD_AXIS (9,  4);      // r^2 = 16
+      if (Ns <= 4) goto finish;
+
+      // r <= 5.0
+      ADD_RAND (10,  1,  4); // r^2 = 17
+      ADD_DIAG (11,  3);     // r^2 = 18
+      ADD_RAND (12,  2,  4); // r^2 = 20
+      ADD_RAND (13,  3,  4); // r^2 = 25
+      ADD_AXIS (13,  5);     // r^2 = 25
+      if (Ns <= 5) goto finish;
+
+      // r <= 6.0
+      ADD_RAND (14,  1,  5); // r^2 = 26
+      ADD_RAND (15,  2,  5); // r^2 = 29
+      ADD_DIAG (16,  4);     // r^2 = 32
+      ADD_RAND (17,  3,  5); // r^2 = 34
+      ADD_AXIS (18,  6);     // r^2 = 36
+      if (Ns <= 6) goto finish;
+
+      // r <= 7.0
+      ADD_RAND (19,  1,  6); // r^2 = 37
+      ADD_RAND (20,  2,  6); // r^2 = 40
+      ADD_RAND (21,  4,  5); // r^2 = 41
+      ADD_RAND (22,  3,  6); // r^2 = 45
+      ADD_AXIS (23,  7);     // r^2 = 49
+      if (Ns <= 7) goto finish;
+
+      // r <= 8.0
+      ADD_RAND (24,  1,  7); // r^2 = 50
+      ADD_DIAG (24,  5);     // r^2 = 50
+      ADD_RAND (25,  4,  6); // r^2 =   52
+      ADD_RAND (26,  2,  7); // r^2 =   53
+      ADD_RAND (27,  3,  7); // r^2 =   58
+      ADD_RAND (28,  5,  6); // r^2 =   61
+      ADD_AXIS (29,  8);     // r^2 =   64
+      if (Ns <= 8) goto finish;
+
+      ADD_RAND (30,  1,  8); // r^2 =   65 *
+      ADD_RAND (30,  4,  7); // r^2 =   65 *
+      ADD_RAND (31,  2,  8); // r^2 =   68
+      ADD_DIAG (32,  6);     // r^2 =   72
+      ADD_RAND (33,  3,  8); // r^2 =   73
+      ADD_RAND (34,  5,  7); // r^2 =   74
+      ADD_RAND (35,  4,  8); // r^2 =   80
+      ADD_AXIS (36,  9);     // r^2 =   81
+      if (Ns <= 9) goto finish;
+
+      ADD_RAND (37,  1,  9); // r^2 =   82
+      ADD_RAND (38,  2,  9); // r^2 =   85 *
+      ADD_RAND (38,  6,  7); // r^2 =   85 *
+      ADD_RAND (39,  5,  8); // r^2 =   89
+      ADD_RAND (40,  3,  9); // r^2 =   90
+      ADD_RAND (41,  4,  9); // r^2 =   97
+      ADD_DIAG (42,  7);     // r^2 =   98
+      ADD_RAND (43,  6,  8); // r^2 =  100 *
+      ADD_AXIS (43, 10);     // r^2 =  100 *
+      if (Ns <= 10) goto finish;
+
+    finish:
+      vo[p] = s;
+    }
+  }
+
+  free (radflux);
+  free (in[0].matrix.buffer);
+  in[0].matrix.buffer = (char *) vo;
+
+  return (TRUE);
+}
+
Index: trunk/Ohana/src/opihi/cmd.data/imsmooth.generic.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/imsmooth.generic.c	(revision 36375)
+++ trunk/Ohana/src/opihi/cmd.data/imsmooth.generic.c	(revision 36375)
@@ -0,0 +1,68 @@
+# include "data.h"
+
+int imsmooth_generic (int argc, char **argv) {
+  
+  int i, j, n;
+  float *vi, *vo;
+  float g, s;
+  Buffer *in;
+  Vector *vec;
+  float *temp;
+
+  if (argc != 3) {
+    gprint (GP_ERR, "USAGE: imsmooth (input) vector\n");
+    return (FALSE);
+  }
+  
+  if ((in  = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) return (FALSE);
+  if ((vec = SelectVector (argv[2], OLDVECTOR, TRUE)) == NULL) return (FALSE);
+
+  if (vec[0].Nelements % 2 == 0) {
+    gprint (GP_ERR, "vector must have an odd number of elements\n");
+    return FALSE;
+  }
+
+  int Nx = in[0].matrix.Naxis[0];
+  int Ny = in[0].matrix.Naxis[1];
+  ALLOCATE (temp, float, Nx*Ny);
+
+  /* build a 1D gaussian */
+  int Ns = 0.5*(vec[0].Nelements - 1);
+  opihi_flt *smvec = &vec[0].elements.Flt[Ns];
+
+  /* smooth in X direction */
+  for (j = 0; j < Ny; j++) {
+    vi = (float *) in[0].matrix.buffer + j*Nx;
+    vo = &temp[j*Nx];
+    for (i = 0; i < Nx; i++) {
+      g = s = 0;
+      for (n = -Ns; n < Ns + 1; n++) {
+	if (i+n < 0) continue;
+	if (i+n >= Nx) continue;
+	s += smvec[n]*vi[i+n];
+	g += smvec[n];
+      }
+      vo[i] = s / g;
+    }
+  }
+
+  /* smooth in Y direction */
+  for (i = 0; i < Nx; i++) {
+    vi = &temp[i];
+    vo = (float *)in[0].matrix.buffer + i;
+    for (j = 0; j < Ny; j++) {
+      g = s = 0;
+      for (n = -Ns; n < Ns + 1; n++) {
+	if (j+n < 0) continue;
+	if (j+n >= Ny) continue;
+	s += smvec[n]*vi[(n+j)*Nx];
+	g += smvec[n];
+      }
+      vo[j*Nx] = s / g;
+    }
+  }
+
+  free (temp);
+  return (TRUE);
+}
+
Index: trunk/Ohana/src/opihi/cmd.data/init.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/init.c	(revision 36374)
+++ trunk/Ohana/src/opihi/cmd.data/init.c	(revision 36375)
@@ -53,4 +53,6 @@
 int imhist           PROTO((int, char **));
 int imsmooth         PROTO((int, char **));
+int imsmooth_generic PROTO((int, char **));
+int imsmooth_2d      PROTO((int, char **));
 int integrate        PROTO((int, char **));
 int interpolate      PROTO((int, char **));
@@ -208,4 +210,6 @@
   {1, "imhistogram",  imhist,           "histogram of an image region"},
   {1, "imsmooth",     imsmooth,         "circular gaussian smoothing"},
+  {1, "imsmooth.generic", imsmooth_generic, "circular non-gaussian smoothing"},
+  {1, "imsmooth.2d",  imsmooth_2d,      "circular non-gaussian smoothing"},
   {1, "imstats",      imstats,          "statistics on a portion of an image"},
   {1, "integrate",    integrate,        "integrate a vector"},
Index: trunk/Ohana/src/opihi/cmd.data/read_vectors.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 36374)
+++ trunk/Ohana/src/opihi/cmd.data/read_vectors.c	(revision 36375)
@@ -23,4 +23,5 @@
 // vector types
 enum {COLTYPE_NONE, COLTYPE_FLT, COLTYPE_INT, COLTYPE_TIME, COLTYPE_CHAR};
+static int FITS_TRANSPOSE;
 
 int read_vectors (int argc, char **argv) {
@@ -34,4 +35,10 @@
 
   char *buffer = NULL;
+
+  FITS_TRANSPOSE = FALSE;
+  if ((Narg = get_argument (argc, argv, "-transpose"))) {
+    remove_argument (Narg, &argc, argv);
+    FITS_TRANSPOSE = TRUE;
+  }
 
   /* auto-sense table type */
@@ -410,60 +417,122 @@
     }
 	
-    // define the multifield vector names
-    ALLOCATE (vec, Vector *, Nval);
-    for (j = 0; j < Nval; j++) {
-      if (Nval == 1) 
-	sprintf (name, "%s", argv[i]);
-      else
-	sprintf (name, "%s:%d", argv[i], j);
-      if ((vec[j] = SelectVector (name, ANYVECTOR, TRUE)) == NULL) ESCAPE ("bad vector name");
-      ResetVector (vec[j], vecType, Ny);
-    }
-
-    if (!strcmp (type, "char")) {
-      char *Ptr = data;
+    if (!FITS_TRANSPOSE) {
+      // define the multifield vector names (Nval vectors x Ny elements)
+      ALLOCATE (vec, Vector *, Nval);
+      for (j = 0; j < Nval; j++) {
+	if (Nval == 1) 
+	  sprintf (name, "%s", argv[i]);
+	else
+	  sprintf (name, "%s:%d", argv[i], j);
+	if ((vec[j] = SelectVector (name, ANYVECTOR, TRUE)) == NULL) ESCAPE ("bad vector name");
+	ResetVector (vec[j], vecType, Ny);
+      }
+
+      if (!strcmp (type, "char")) {
+	char *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[k][0].elements.Int[j] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "short")) {
+	short *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[k][0].elements.Int[j] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "int")) {
+	int *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[k][0].elements.Int[j] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "int64_t")) {
+	int64_t *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[k][0].elements.Int[j] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "float")) {
+	float *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[k][0].elements.Flt[j] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "double")) {
+	double *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[k][0].elements.Flt[j] = *Ptr;
+	  }
+	}
+      }
+    } else {
+      // define the multifield vector names (Ny vectors x Nval elements)
+      ALLOCATE (vec, Vector *, Ny);
       for (j = 0; j < Ny; j++) {
-	for (k = 0; k < Nval; k++, Ptr++) {
-	  vec[k][0].elements.Int[j] = *Ptr;
-	}
-      }
-    }
-    if (!strcmp (type, "short")) {
-      short *Ptr = data;
-      for (j = 0; j < Ny; j++) {
-	for (k = 0; k < Nval; k++, Ptr++) {
-	  vec[k][0].elements.Int[j] = *Ptr;
-	}
-      }
-    }
-    if (!strcmp (type, "int")) {
-      int *Ptr = data;
-      for (j = 0; j < Ny; j++) {
-	for (k = 0; k < Nval; k++, Ptr++) {
-	  vec[k][0].elements.Int[j] = *Ptr;
-	}
-      }
-    }
-    if (!strcmp (type, "int64_t")) {
-      int64_t *Ptr = data;
-      for (j = 0; j < Ny; j++) {
-	for (k = 0; k < Nval; k++, Ptr++) {
-	  vec[k][0].elements.Int[j] = *Ptr;
-	}
-      }
-    }
-    if (!strcmp (type, "float")) {
-      float *Ptr = data;
-      for (j = 0; j < Ny; j++) {
-	for (k = 0; k < Nval; k++, Ptr++) {
-	  vec[k][0].elements.Flt[j] = *Ptr;
-	}
-      }
-    }
-    if (!strcmp (type, "double")) {
-      double *Ptr = data;
-      for (j = 0; j < Ny; j++) {
-	for (k = 0; k < Nval; k++, Ptr++) {
-	  vec[k][0].elements.Flt[j] = *Ptr;
+	if (Ny == 1) 
+	  sprintf (name, "%s", argv[i]);
+	else
+	  sprintf (name, "%s:%d", argv[i], j);
+	if ((vec[j] = SelectVector (name, ANYVECTOR, TRUE)) == NULL) ESCAPE ("bad vector name");
+	ResetVector (vec[j], vecType, Nval);
+      }
+
+      if (!strcmp (type, "char")) {
+	char *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[j][0].elements.Int[k] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "short")) {
+	short *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[j][0].elements.Int[k] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "int")) {
+	int *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[j][0].elements.Int[k] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "int64_t")) {
+	int64_t *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[j][0].elements.Int[k] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "float")) {
+	float *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[j][0].elements.Flt[k] = *Ptr;
+	  }
+	}
+      }
+      if (!strcmp (type, "double")) {
+	double *Ptr = data;
+	for (j = 0; j < Ny; j++) {
+	  for (k = 0; k < Nval; k++, Ptr++) {
+	    vec[j][0].elements.Flt[k] = *Ptr;
+	  }
 	}
       }
Index: trunk/Ohana/src/opihi/dvo/Makefile
===================================================================
--- trunk/Ohana/src/opihi/dvo/Makefile	(revision 36374)
+++ trunk/Ohana/src/opihi/dvo/Makefile	(revision 36375)
@@ -40,4 +40,5 @@
 $(SRC)/cmpload.$(ARCH).o	  	\
 $(SRC)/cmpread.$(ARCH).o	  	\
+$(SRC)/coordimage.$(ARCH).o	  	\
 $(SRC)/ddmags.$(ARCH).o	  	\
 $(SRC)/detrend.$(ARCH).o	  	\
Index: trunk/Ohana/src/opihi/dvo/coordimage.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/coordimage.c	(revision 36375)
+++ trunk/Ohana/src/opihi/dvo/coordimage.c	(revision 36375)
@@ -0,0 +1,143 @@
+# include "dvoshell.h"
+
+int coordimage (int argc, char **argv) {
+
+  int N;
+  off_t i, j, Nimage, *subset, Nsubset;
+  time_t tzero;
+  double trange;
+  Buffer *bufX, *bufY;
+  SkyRegionSelection *selection;
+  Image *image;
+
+  if (!InitPhotcodes ()) return (FALSE);
+
+  // parse skyregion options
+  if ((selection = SetRegionSelection (&argc, argv)) == NULL) {
+    gprint (GP_ERR, "invalid sky region selection\n");
+    return FALSE;
+  }
+
+  int TimeSelect = FALSE;
+  if ((N = get_argument (argc, argv, "-time"))) {
+    remove_argument (N, &argc, argv);
+    if (!ohana_str_to_time (argv[N], &tzero)) { 
+      gprint (GP_ERR, "syntax error\n");
+      return (FALSE);
+    }
+    remove_argument (N, &argc, argv);
+    if (!ohana_str_to_dtime (argv[N], &trange)) { 
+      gprint (GP_ERR, "syntax error\n");
+      return (FALSE);
+    }
+    remove_argument (N, &argc, argv);
+    TimeSelect = TRUE;
+    gprint (GP_ERR, "plotting in range %ds - %ds (%f seconds)\n", (int)tzero, (int)(tzero + trange), trange);
+  }
+
+  PhotCode *Photcode = NULL;
+  if ((N = get_argument (argc, argv, "-photcode"))) {
+    remove_argument (N, &argc, argv);
+    Photcode = GetPhotcodebyName (argv[N]);
+    if (Photcode == NULL) {
+      gprint (GP_ERR, "photcode not found in photcode table\n");
+      return (FALSE);
+    }
+    remove_argument (N, &argc, argv);
+  }
+  if ((N = get_argument (argc, argv, "-Nphotcode"))) {
+    remove_argument (N, &argc, argv);
+    Photcode = GetPhotcodebyCode (atoi(argv[N]));
+    if (Photcode == NULL) {
+      gprint (GP_ERR, "photcode not found in photcode table\n");
+      return (FALSE);
+    }
+    remove_argument (N, &argc, argv);
+  }
+
+  char *Name = NULL;
+  if ((N = get_argument (argc, argv, "-name"))) {
+    remove_argument (N, &argc, argv);
+    Name = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  int chipID = -1;
+  if ((N = get_argument (argc, argv, "-chip"))) {
+    remove_argument (N, &argc, argv);
+    chipID = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  
+  if (argc != 5) goto syntax;
+
+  if ((bufX = SelectBuffer (argv[1], ANYBUFFER, TRUE)) == NULL) goto escape;
+  if ((bufY = SelectBuffer (argv[2], ANYBUFFER, TRUE)) == NULL) goto escape;
+  int Nx = atoi (argv[3]);
+  int Ny = atoi (argv[4]);
+
+  gfits_free_matrix (&bufX[0].matrix);
+  gfits_free_header (&bufX[0].header);
+  CreateBuffer (bufX, Nx, Ny, -32, 0.0, 1.0); // initialized to 0.0 here
+  strcpy (bufX[0].file, "(empty)");
+
+  gfits_free_matrix (&bufY[0].matrix);
+  gfits_free_header (&bufY[0].header);
+  CreateBuffer (bufY, Nx, Ny, -32, 0.0, 1.0); // initialized to 0.0 here
+  strcpy (bufY[0].file, "(empty)");
+
+  if ((image = LoadImagesDVO (&Nimage)) == NULL) return (FALSE);
+  image_subset (image, Nimage, &subset, &Nsubset, selection, tzero, trange, TimeSelect);
+
+  float Nsum = 0.0;
+  float *vX = (float *) bufX[0].matrix.buffer;
+  float *vY = (float *) bufY[0].matrix.buffer;
+
+  for (j = 0; j < Nsubset; j++) {
+    i = subset[j];
+    if (Name && (strstr (image[i].name, Name) == (char *) NULL)) continue;
+    if (Photcode) {
+      if (Photcode[0].type == PHOT_DEP) {
+	if (Photcode[0].code != image[i].photcode) continue;
+      } else {
+	if (Photcode[0].code != GetPhotcodeEquivCodebyCode (image[i].photcode)) continue;
+      }
+    }
+    if (chipID > -1) {
+      if (image[i].ccdnum != chipID) continue;
+    }
+
+    float dX = image[i].NX / (float) Nx;
+    float dY = image[i].NY / (float) Ny;
+
+    Coords coords = image[i].coords;
+    coords.Npolyterms = 0;
+
+    int ix, iy;
+    for (iy = 0; iy < Ny; iy++) {
+      double fy = iy * dY; // coordinate in full image
+      for (ix = 0; ix < Nx; ix++) {
+	double fx = ix * dX; // coordinate in full image
+
+	double Lo, Lm, dL;
+	double Mo, Mm, dM;
+
+	XY_to_LM (&Lo, &Mo, fx, fy, &coords);
+	XY_to_LM (&Lm, &Mm, fx, fy, &image[i].coords);
+	dL = Lo - Lm;
+	dM = Mo - Mm;
+
+	vX[ix + iy*Nx] += dL;
+	vY[ix + iy*Nx] += dM;
+      }
+    }
+    Nsum ++;
+  }
+  fprintf (stderr, "Nsum: %f\n", Nsum);
+  return TRUE;
+
+ syntax:
+  gprint (GP_ERR, "USAGE: coordimage buffX buffY Nx Ny\n");
+ escape:
+  return (FALSE);
+}
Index: trunk/Ohana/src/opihi/dvo/init.c
===================================================================
--- trunk/Ohana/src/opihi/dvo/init.c	(revision 36374)
+++ trunk/Ohana/src/opihi/dvo/init.c	(revision 36375)
@@ -13,4 +13,5 @@
 int cmpload         PROTO((int, char **));
 int cmpread         PROTO((int, char **));
+int coordimage      PROTO((int, char **));
 int ddmags          PROTO((int, char **));
 int detrend         PROTO((int, char **));
@@ -71,4 +72,5 @@
   {1, "cmpload",     cmpload,      "load cmp file into ?"},
   {1, "cmpread",     cmpread,      "read data from cmp format files"},
+  {1, "coordimage",  coordimage,   "generate a map of the transformation residuals"},
   {1, "ddmags",      ddmags,       "plot magnitude differences"},
   {1, "detrend",     detrend,      "extract from detrend database?"},
Index: trunk/Ohana/src/opihi/include/dvomath.h
===================================================================
--- trunk/Ohana/src/opihi/include/dvomath.h	(revision 36374)
+++ trunk/Ohana/src/opihi/include/dvomath.h	(revision 36375)
@@ -55,7 +55,35 @@
 } Buffer;
 
+typedef enum {
+  ST_NONE,
+  ST_LEFT,
+  ST_RIGHT,
+  ST_COMMA,
+  ST_TRINARY,
+  ST_OR,
+  ST_AND,
+  ST_LOGIC,
+  ST_BITWISE,
+  ST_ADD,
+  ST_TIMES,
+  ST_POWER,
+  ST_UNARY,
+  ST_BINARY,
+
+  ST_VALUE,
+  ST_SCALAR_INT,
+  ST_SCALAR_FLT,
+  ST_VECTOR,
+  ST_VECTOR_TMP,
+  ST_MATRIX,
+  ST_MATRIX_TMP,
+
+  ST_STRING,
+  ST_STRING_TMP,
+} StackVarType;
+
 typedef struct {			/* math stack structure */
   char   *name;
-  char    type;
+  StackVarType type;
   Buffer *buffer;
   Vector *vector;
@@ -77,5 +105,9 @@
 void          delete_stack	    PROTO((StackVar *stack, int Nstack));
 void          clear_stack 	    PROTO((StackVar *stack));
-void          assign_stack 	    PROTO((StackVar *stack, char *name, int type));
+void          assign_stack 	    PROTO((StackVar *stack, char *name, StackVarType type));
+
+int           SSS_trinary           PROTO((StackVar *OUT, StackVar *V1, StackVar *V2, StackVar *V3, char *op));
+int           VVV_trinary           PROTO((StackVar *OUT, StackVar *V1, StackVar *V2, StackVar *V3, char *op));
+int           MMM_trinary           PROTO((StackVar *OUT, StackVar *V1, StackVar *V2, StackVar *V3, char *op));
 
 int           VV_binary             PROTO((StackVar *OUT, StackVar *V1, StackVar *V2, char *op));
Index: trunk/Ohana/src/opihi/include/pcontrol.h
===================================================================
--- trunk/Ohana/src/opihi/include/pcontrol.h	(revision 36374)
+++ trunk/Ohana/src/opihi/include/pcontrol.h	(revision 36375)
@@ -8,4 +8,5 @@
 /** job status values **/
 typedef enum {
+  PCONTROL_JOB_NONE, // XXX OK?
   PCONTROL_JOB_ALLJOBS,
   PCONTROL_JOB_PENDING,
@@ -309,4 +310,5 @@
 char  *GetJobStackName (int StackID);
 Stack *GetJobStackByName (char *name);
+int GetJobStackIDbyName (char *name);
 int    PutJob (Job *job, int StackID, int where);
 int    PutJobSetState (Job *job, int StackID, int where, int state);
Index: trunk/Ohana/src/opihi/lib.shell/check_stack.c
===================================================================
--- trunk/Ohana/src/opihi/lib.shell/check_stack.c	(revision 36374)
+++ trunk/Ohana/src/opihi/lib.shell/check_stack.c	(revision 36375)
@@ -12,5 +12,5 @@
 
   for (i = 0; i < Nstack; i++) {
-    if (stack[i].type == 'X') {
+    if (stack[i].type == ST_VALUE) {
 
       /** if this is a number, put it on the list of scalars and move on.  assume value is
@@ -27,13 +27,13 @@
       stack[i].IntValue = strtol (stack[i].name, &c2, 0);
       if ((fabs(stack[i].FltValue) > MAX_INT) && (c1 == stack[i].name + strlen (stack[i].name))) {
-	stack[i].type  = 'S'; // 'S' == (float)
+	stack[i].type  = ST_SCALAR_FLT; // (float)
 	continue;
       } 
       if (c2 == stack[i].name + strlen (stack[i].name)) {
-	stack[i].type  = 's'; // 's' == (int)
+	stack[i].type  = ST_SCALAR_INT; // (int)
 	continue;
       } 
       if (c1 == stack[i].name + strlen (stack[i].name)) {
-	stack[i].type  = 'S'; // 'S' == (float)
+	stack[i].type  = ST_SCALAR_FLT; // (float)
 	continue;
       } 
@@ -42,5 +42,5 @@
       if (IsBuffer (stack[i].name)) {
 	stack[i].buffer = SelectBuffer (stack[i].name, OLDBUFFER, TRUE);
-	stack[i].type   = 'M';
+	stack[i].type   = ST_MATRIX;
 	if (Nx == -1) {
 	  Nx = stack[i].buffer[0].matrix.Naxis[0];
@@ -63,5 +63,5 @@
       if (IsVector (stack[i].name)) {
 	stack[i].vector = SelectVector (stack[i].name, OLDVECTOR, FALSE);
-	stack[i].type   = 'V';
+	stack[i].type   = ST_VECTOR;
 
 	if (Nv == -1) Nv = stack[i].vector[0].Nelements;
@@ -80,5 +80,5 @@
 
       /* this is not a scalar, vector, or matrix.  must be string */
-      stack[i].type  = 'W';
+      stack[i].type  = ST_STRING;
     }
   }
Index: trunk/Ohana/src/opihi/lib.shell/convert_to_RPN.c
===================================================================
--- trunk/Ohana/src/opihi/lib.shell/convert_to_RPN.c	(revision 36374)
+++ trunk/Ohana/src/opihi/lib.shell/convert_to_RPN.c	(revision 36375)
@@ -4,5 +4,5 @@
 StackVar *convert_to_RPN (int argc, char **argv, int *nstack) {
   
-  int type;
+ StackVarType type;
   int i, j, Nstack, Nop_stack, NSTACK;
   StackVar *stack, *op_stack;
@@ -21,78 +21,82 @@
     
     /* decide on priority of object */
-    type = 0;
+    type = ST_NONE;
+
+    /* trinary operations */
+    if (!strcmp (argv[i], "?"))      { type = ST_TRINARY; goto gotit; }
+    if (!strcmp (argv[i], ":"))      { type = ST_COMMA; goto gotit; }
+
     /* unary operations */
-    if (!strcmp (argv[i], "abs"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "int"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "exp"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "ten"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "log"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "ln"))     { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "sqrt"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "erf"))    { type = 9; goto gotit; }
-
-    if (!strcmp (argv[i], "sinh"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "cosh"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "asinh"))  { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "acosh"))  { type = 9; goto gotit; }
-
-    if (!strcmp (argv[i], "sin"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "cos"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "tan"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "dsin"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "dcos"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "dtan"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "asin"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "acos"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "atan"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "dasin"))  { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "dacos"))  { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "datan"))  { type = 9; goto gotit; }
-
-    if (!strcmp (argv[i], "lgamma")) { type = 9; goto gotit; }
-
-    if (!strcmp (argv[i], "rnd"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "xramp"))  { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "yramp"))  { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "ramp"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "zero"))   { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "--"))     { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "not"))    { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "isinf"))  { type = 9; goto gotit; }
-    if (!strcmp (argv[i], "isnan"))  { type = 9; goto gotit; }
+    if (!strcmp (argv[i], "abs"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "int"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "exp"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "ten"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "log"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "ln"))     { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "sqrt"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "erf"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "sinh"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "cosh"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "asinh"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "acosh"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "sin"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "cos"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "tan"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "dsin"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "dcos"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "dtan"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "asin"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "acos"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "atan"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "dasin"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "dacos"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "datan"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "lgamma")) { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "rnd"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "xramp"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "yramp"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "ramp"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "zero"))   { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "--"))     { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "not"))    { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "isinf"))  { type = ST_UNARY; goto gotit; }
+    if (!strcmp (argv[i], "isnan"))  { type = ST_UNARY; goto gotit; }
 
     /* binary operations */
-    if (!strcmp (argv[i], "^"))      { type = 8; goto gotit; }
-
-    if (!strcmp (argv[i], "@"))      { type = 7; goto gotit; }
-    if (!strcmp (argv[i], "/"))      { type = 7; goto gotit; }
-    if (!strcmp (argv[i], "*"))      { type = 7; goto gotit; }
-    if (!strcmp (argv[i], "%"))      { type = 7; goto gotit; }
-
-    if (!strcmp (argv[i], "+"))      { type = 6; goto gotit; }
-    if (!strcmp (argv[i], "-"))      { type = 6; goto gotit; }
+    if (!strcmp (argv[i], "^"))      { type = ST_POWER; goto gotit; }
+
+    if (!strcmp (argv[i], "atan2"))  { type = ST_BINARY; goto gotit; }
+    if (!strcmp (argv[i], ","))      { type = ST_COMMA; goto gotit; }
+
+    if (!strcmp (argv[i], "@"))      { type = ST_TIMES; goto gotit; }
+    if (!strcmp (argv[i], "/"))      { type = ST_TIMES; goto gotit; }
+    if (!strcmp (argv[i], "*"))      { type = ST_TIMES; goto gotit; }
+    if (!strcmp (argv[i], "%"))      { type = ST_TIMES; goto gotit; }
+
+    if (!strcmp (argv[i], "+"))      { type = ST_ADD; goto gotit; }
+    if (!strcmp (argv[i], "-"))      { type = ST_ADD; goto gotit; }
 	
-    if (!strcmp (argv[i], "&"))      { type = 5; goto gotit; }
-    if (!strcmp (argv[i], "|"))      { type = 5; goto gotit; }
-
-    if (!strcmp (argv[i], "<"))      { type = 4; goto gotit; }
-    if (!strcmp (argv[i], ">"))      { type = 4; goto gotit; }
-    if (!strcmp (argv[i], "=="))     { type = 4; strcpy (argv[i], "E"); goto gotit; }
-    if (!strcmp (argv[i], "!="))     { type = 4; strcpy (argv[i], "N"); goto gotit; }
-    if (!strcmp (argv[i], "<="))     { type = 4; strcpy (argv[i], "L"); goto gotit; }
-    if (!strcmp (argv[i], ">="))     { type = 4; strcpy (argv[i], "G"); goto gotit; }
-    if (!strcmp (argv[i], ">>"))     { type = 4; strcpy (argv[i], "U"); goto gotit; }
-    if (!strcmp (argv[i], "<<"))     { type = 4; strcpy (argv[i], "D"); goto gotit; }
-
-    if (!strcmp (argv[i], "&&"))     { type = 3; strcpy (argv[i], "A"); goto gotit; }
-    if (!strcmp (argv[i], "||"))     { type = 3; strcpy (argv[i], "O"); goto gotit; }
-
-    if (!strcmp (argv[i], "("))      { type = 2; goto gotit; }
-    if (!strcmp (argv[i], ")"))      { type = 1; goto gotit; }
+    if (!strcmp (argv[i], "&"))      { type = ST_BITWISE; goto gotit; }
+    if (!strcmp (argv[i], "|"))      { type = ST_BITWISE; goto gotit; }
+
+    if (!strcmp (argv[i], "<"))      { type = ST_LOGIC; goto gotit; }
+    if (!strcmp (argv[i], ">"))      { type = ST_LOGIC; goto gotit; }
+    if (!strcmp (argv[i], "=="))     { type = ST_LOGIC; strcpy (argv[i], "E"); goto gotit; }
+    if (!strcmp (argv[i], "!="))     { type = ST_LOGIC; strcpy (argv[i], "N"); goto gotit; }
+    if (!strcmp (argv[i], "<="))     { type = ST_LOGIC; strcpy (argv[i], "L"); goto gotit; }
+    if (!strcmp (argv[i], ">="))     { type = ST_LOGIC; strcpy (argv[i], "G"); goto gotit; }
+    if (!strcmp (argv[i], ">>"))     { type = ST_LOGIC; strcpy (argv[i], "U"); goto gotit; }
+    if (!strcmp (argv[i], "<<"))     { type = ST_LOGIC; strcpy (argv[i], "D"); goto gotit; }
+
+    if (!strcmp (argv[i], "&&"))     { type = ST_AND; strcpy (argv[i], "A"); goto gotit; }
+    if (!strcmp (argv[i], "||"))     { type = ST_OR ; strcpy (argv[i], "O"); goto gotit; }
+
+    if (!strcmp (argv[i], "("))      { type = ST_LEFT;  goto gotit; }
+    if (!strcmp (argv[i], ")"))      { type = ST_RIGHT; goto gotit; }
 
   gotit:
     /* choose how to deal with object */
     switch (type) {
-      case 8:  /* exponentiation: 2^2^3 = 64 != 256 (precedence is right-to-left, not left-to-right!) */
+      case ST_POWER:  /* exponentiation: 2^2^3 = 64 != 256 (precedence is right-to-left, not left-to-right!) */
 	/* pop previous, higher operators from OP stack to stack */
 	for (j = Nop_stack - 1; (j >= 0) && (op_stack[j].type > type); j--) {
@@ -105,10 +109,13 @@
 	Nop_stack ++;
 	break;
-      case 9: /* unary OPs */
-      case 7: /* binary OPs */
-      case 6:
-      case 5: 
-      case 4: 
-      case 3: 
+      case ST_UNARY: 
+      case ST_BINARY: 
+      case ST_TRINARY:
+      case ST_TIMES:
+      case ST_ADD:
+      case ST_BITWISE: 
+      case ST_LOGIC: 
+      case ST_AND: 
+      case ST_OR: 
 	/* pop previous, higher or equal operators from OP stack to stack */
 	for (j = Nop_stack - 1; (j >= 0) && (op_stack[j].type >= type); j--) {
@@ -121,5 +128,5 @@
 	Nop_stack ++;
 	break;
-      case 2:  
+      case ST_LEFT:  
 	/* push operator on OP stack */
 	assign_stack (&op_stack[Nop_stack], argv[i], type);
@@ -127,12 +134,12 @@
 	Nop_stack ++;
 	break;
-      case 1: 
+      case ST_RIGHT: 
 	/* pop rest of operators from OP stack to stack, looking for '(' */
-	for (j = Nop_stack - 1; (j >= 0) && (op_stack[j].type != 2); j--) {
-	  move_stack (&stack[Nstack], &op_stack[j]);
-	  Nstack ++;
-	  Nop_stack --;
-	}
-	if ((j == -1) || (op_stack[j].type != 2)) {
+	for (j = Nop_stack - 1; (j >= 0) && (op_stack[j].type != ST_LEFT); j--) {
+	  move_stack (&stack[Nstack], &op_stack[j]);
+	  Nstack ++;
+	  Nop_stack --;
+	}
+	if ((j == -1) || (op_stack[j].type != ST_LEFT)) {
 	  push_error ("syntax error: mismatched parenthesis");
 	  Nstack = 0;
@@ -144,16 +151,29 @@
 	Nop_stack --;
 	break;
-      case 0:
+      case ST_COMMA: 
+	/* pop rest of operators from OP stack to stack, looking for '(' (but do not pop the '(')*/
+	for (j = Nop_stack - 1; (j >= 0) && (op_stack[j].type != ST_LEFT) && (op_stack[j].type != ST_TRINARY); j--) {
+	  move_stack (&stack[Nstack], &op_stack[j]);
+	  Nstack ++;
+	  Nop_stack --;
+	}
+	break;
+      case ST_NONE:
 	/* place the value (number or vector/matrix name) on stack */
 	/* value of 'X' is used as sentinel until we sort out values */
-	assign_stack (&stack[Nstack], argv[i], 'X');
+	assign_stack (&stack[Nstack], argv[i], ST_VALUE);
 	Nstack ++;
 	break;
+
+      default:
+	push_error ("invalid stack typ");
+	Nstack = 0;
+	goto cleanup;
     }
   }
 
-  /* dump remaining operators on stack, checking for ')' */
+  /* dump remaining operators on stack, checking for '(' */
   for (j = Nop_stack - 1; j >= 0; j--) {
-    if (op_stack[j].type == 2) {
+    if (op_stack[j].type == ST_LEFT) {
       push_error ("syntax error: mismatched parenthesis");
       Nstack = 0;
Index: trunk/Ohana/src/opihi/lib.shell/dvomath.c
===================================================================
--- trunk/Ohana/src/opihi/lib.shell/dvomath.c	(revision 36374)
+++ trunk/Ohana/src/opihi/lib.shell/dvomath.c	(revision 36375)
@@ -80,5 +80,5 @@
 	sprintf (outname, "%s", stack[0].name);
       } else {
-	if (stack[0].type == 's') {
+	if (stack[0].type == ST_SCALAR_INT) {
 	  sprintf (outname, "%d", stack[0].IntValue);
 	} else {
Index: trunk/Ohana/src/opihi/lib.shell/evaluate_stack.c
===================================================================
--- trunk/Ohana/src/opihi/lib.shell/evaluate_stack.c	(revision 36374)
+++ trunk/Ohana/src/opihi/lib.shell/evaluate_stack.c	(revision 36375)
@@ -2,11 +2,51 @@
 # define VERBOSE 0
 
-# define TWO_OP(A,B,FUNC) \
-  if (!strncasecmp (&stack[i - 2].type, A, 1) && !strncasecmp (&stack[i - 1].type, B, 1)) \
-    status = FUNC (&tmp_stack, &stack[i - 2], &stack[i - 1], stack[i].name); 
-
-# define ONE_OP(A,FUNC) \
-  if (!strncasecmp (&stack[i - 1].type, A, 1)) \
-    status = FUNC (&tmp_stack, &stack[i - 1], stack[i].name); 
+// all three operands must have the same type
+# define THREE_OP(A,FUNC) 						\
+  if ((stack[i - 3].type == A) && (stack[i - 2].type == A) && (stack[i - 1].type == A)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+  if ((stack[i - 3].type == A+1) && (stack[i - 2].type == A) && (stack[i - 1].type == A)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+  if ((stack[i - 3].type == A) && (stack[i - 2].type == A+1) && (stack[i - 1].type == A)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+  if ((stack[i - 3].type == A+1) && (stack[i - 2].type == A+1) && (stack[i - 1].type == A)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+  if ((stack[i - 3].type == A) && (stack[i - 2].type == A) && (stack[i - 1].type == A+1)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+  if ((stack[i - 3].type == A+1) && (stack[i - 2].type == A) && (stack[i - 1].type == A+1)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+  if ((stack[i - 3].type == A) && (stack[i - 2].type == A+1) && (stack[i - 1].type == A+1)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+  if ((stack[i - 3].type == A+1) && (stack[i - 2].type == A+1) && (stack[i - 1].type == A+1)) { \
+    status = FUNC (&tmp_stack, &stack[i - 3], &stack[i - 2], &stack[i - 1], stack[i].name); \
+    goto got_three_op; } \
+
+// A & B value types all have 2 possible values
+# define TWO_OP(A,B,FUNC) {						\
+    if ((stack[i - 2].type == A) && (stack[i - 1].type == B)) {		\
+      status = FUNC (&tmp_stack, &stack[i - 2], &stack[i - 1], stack[i].name); \
+      goto got_two_op; }						\
+    if ((stack[i - 2].type == A+1) && (stack[i - 1].type == B)) {	\
+      status = FUNC (&tmp_stack, &stack[i - 2], &stack[i - 1], stack[i].name); \
+      goto got_two_op; }						\
+    if ((stack[i - 2].type == A) && (stack[i - 1].type == B+1)) {	\
+      status = FUNC (&tmp_stack, &stack[i - 2], &stack[i - 1], stack[i].name); \
+      goto got_two_op; }						\
+    if ((stack[i - 2].type == A+1) && (stack[i - 1].type == B+1)) {	\
+      status = FUNC (&tmp_stack, &stack[i - 2], &stack[i - 1], stack[i].name); \
+      goto got_two_op; } \
+  }
+
+# define ONE_OP(A,FUNC)						\
+  if (stack[i - 1].type == A) {					\
+    status = FUNC (&tmp_stack, &stack[i - 1], stack[i].name);   \
+    goto got_one_op; }
 
 int evaluate_stack (StackVar *stack, int *Nstack) {
@@ -20,9 +60,9 @@
 
   if (*Nstack == 1) {
-    if ((stack[0].type == 'S') || (stack[0].type == 's')) {
+    if ((stack[0].type == ST_SCALAR_INT) || (stack[0].type == ST_SCALAR_FLT)) {
       clear_stack (&tmp_stack);
       return (TRUE);
     }
-    if (stack[0].type == 'V') {
+    if (stack[0].type == ST_VECTOR) {
       /* need to make a copy so we set output value? */
       V_unary (&tmp_stack, &stack[0], "=");
@@ -30,5 +70,5 @@
       return (TRUE);
     }
-    if (stack[0].type == 'M') {
+    if (stack[0].type == ST_MATRIX) {
       /* need to make a copy so we set output value? */
       M_unary (&tmp_stack, &stack[0], "=");
@@ -56,75 +96,148 @@
     }
 
-    /***** binary operators *****/
-    if ((stack[i].type >= 3) && (stack[i].type <= 8)) {
-
-      if (i < 2) {  /* need two variables to operate on */
-	sprintf (line, "syntax error: binary operator with one operand: %s\n", stack[i].name);
+    /***** trinary operators *****/
+    switch (stack[i].type) {
+      case ST_TRINARY:
+
+	if (i < 3) {  /* need two variables to operate on */
+	  snprintf (line, 512, "syntax error: trinary operator without three operands: %s\n", stack[i].name);
+	  push_error (line);
+	  clear_stack (&tmp_stack);
+	  return (FALSE);
+	}
+
+	status = FALSE;
+	THREE_OP (ST_MATRIX,MMM_trinary);
+	THREE_OP (ST_VECTOR,VVV_trinary);
+
+	THREE_OP (ST_SCALAR_FLT,SSS_trinary);
+	THREE_OP (ST_SCALAR_INT,SSS_trinary);
+
+	/* there are no valid unary string operators */
+	push_error ("invalid operands for trinary operator (mismatch types?)");
+	clear_stack (&tmp_stack);
+	return (FALSE);
+
+      got_three_op:
+	if (!status) {
+	  snprintf (line, 512, "syntax error: invalid operand for binary operation: %s or %s or %s\n", stack[i-1].name, stack[i-2].name, stack[i-3].name);
+	  push_error (line);
+	  clear_stack (&tmp_stack);
+	  return (FALSE);
+	}
+	move_stack (&stack[i-3], &tmp_stack);
+	delete_stack (&stack[i-2], 3);
+	for (j = i + 1; j < *Nstack; j++) {
+	  move_stack (&stack[j-3], &stack[j]);
+	}
+	*Nstack -= 3;
+	i -= 3;
+	init_stack (&tmp_stack);
+	continue;
+
+	/***** binary operators *****/
+      case ST_OR:
+      case ST_AND:
+      case ST_LOGIC:
+      case ST_BITWISE:
+      case ST_ADD:
+      case ST_TIMES:
+      case ST_POWER:
+      case ST_BINARY:
+
+	if (i < 2) {  /* need two variables to operate on */
+	  snprintf (line, 512, "syntax error: binary operator with one operand: %s\n", stack[i].name);
+	  push_error (line);
+	  clear_stack (&tmp_stack);
+	  return (FALSE);
+	}
+
+	status = FALSE;
+	TWO_OP (ST_MATRIX,ST_MATRIX,MM_binary);
+	TWO_OP (ST_MATRIX,ST_VECTOR,MV_binary);
+	TWO_OP (ST_MATRIX,ST_SCALAR_INT,MS_binary);
+
+	TWO_OP (ST_VECTOR,ST_MATRIX,VM_binary);
+	TWO_OP (ST_VECTOR,ST_VECTOR,VV_binary);
+	TWO_OP (ST_VECTOR,ST_SCALAR_INT,VS_binary);
+
+	TWO_OP (ST_SCALAR_INT,ST_MATRIX,SM_binary);
+	TWO_OP (ST_SCALAR_INT,ST_VECTOR,SV_binary);
+	TWO_OP (ST_SCALAR_INT,ST_SCALAR_INT,SS_binary);      
+
+	TWO_OP (ST_SCALAR_FLT,ST_MATRIX,SM_binary);
+	TWO_OP (ST_SCALAR_FLT,ST_VECTOR,SV_binary);
+	TWO_OP (ST_SCALAR_FLT,ST_SCALAR_INT,SS_binary);      
+
+	TWO_OP (ST_STRING,ST_STRING,WW_binary);      
+	TWO_OP (ST_STRING,ST_SCALAR_INT,WW_binary);      
+	TWO_OP (ST_SCALAR_INT,ST_STRING,WW_binary);      
+      
+      got_two_op:
+	if (!status) {
+	  snprintf (line, 512, "syntax error: invalid operand for binary operation: %s or %s\n", stack[i-1].name, stack[i-2].name);
+	  push_error (line);
+	  clear_stack (&tmp_stack);
+	  return (FALSE);
+	}
+	move_stack (&stack[i-2], &tmp_stack);
+	delete_stack (&stack[i-1], 2);
+	for (j = i + 1; j < *Nstack; j++) {
+	  move_stack (&stack[j - 2], &stack[j]);
+	}
+	*Nstack -= 2;
+	i -= 2;
+	init_stack (&tmp_stack);
+	continue;
+
+	/***** unary operators **/
+      case ST_UNARY:
+
+	if (i < 1) {  /* need one variable to operate on */
+	  push_error ("syntax error: unary operator with no operand");
+	  clear_stack (&tmp_stack);
+	  return (FALSE);
+	}
+
+	ONE_OP (ST_MATRIX, M_unary);
+	ONE_OP (ST_MATRIX_TMP, M_unary);
+
+	ONE_OP (ST_VECTOR, V_unary);
+	ONE_OP (ST_VECTOR_TMP, V_unary);
+
+	ONE_OP (ST_SCALAR_INT, S_unary);
+	ONE_OP (ST_SCALAR_FLT, S_unary);
+
+	/* there are no valid unary string operators */
+	push_error ("syntax error: no valid string unary ops");
+	clear_stack (&tmp_stack);
+	return (FALSE);
+
+      got_one_op:
+	move_stack (&stack[i-1], &tmp_stack);
+	delete_stack (&stack[i], 1);
+	for (j = i + 1; j < *Nstack; j++) {
+	  move_stack (&stack[j - 1], &stack[j]);
+	}
+	init_stack (&tmp_stack);
+	*Nstack -= 1;
+	i -= 1;
+	continue;
+
+      case ST_SCALAR_INT:
+      case ST_SCALAR_FLT:
+      case ST_VECTOR:
+      case ST_VECTOR_TMP:
+      case ST_MATRIX:
+      case ST_MATRIX_TMP:
+      case ST_STRING:
+	continue;
+
+      default:
+	snprintf (line, 512, "syntax error: unexpected operator type %s", stack[i].name);
 	push_error (line);
 	clear_stack (&tmp_stack);
 	return (FALSE);
-      }
-
-      status = FALSE;
-      TWO_OP ("M","M",MM_binary);
-      TWO_OP ("M","V",MV_binary);
-      TWO_OP ("M","S",MS_binary);
-      TWO_OP ("V","M",VM_binary);
-      TWO_OP ("V","V",VV_binary);
-      TWO_OP ("V","S",VS_binary);
-      TWO_OP ("S","M",SM_binary);
-      TWO_OP ("S","V",SV_binary);
-      TWO_OP ("S","S",SS_binary);      
-      TWO_OP ("W","W",WW_binary);      
-      TWO_OP ("W","S",WW_binary);      
-      TWO_OP ("S","W",WW_binary);      
-      
-      if (!status) {
-	sprintf (line, "syntax error: invalid operand for binary operation: %s or %s\n", stack[i-1].name, stack[i-2].name);
-	push_error (line);
-	clear_stack (&tmp_stack);
-	return (FALSE);
-      }
-      move_stack (&stack[i-2], &tmp_stack);
-      delete_stack (&stack[i-1], 2);
-      for (j = i + 1; j < *Nstack; j++) {
-	move_stack (&stack[j - 2], &stack[j]);
-      }
-      *Nstack -= 2;
-      i -= 2;
-      init_stack (&tmp_stack);
-      continue;
-    }
-
-    /***** unary operators **/
-    if (stack[i].type == 9) {
-
-      if (i < 1) {  /* need one variable to operate on */
-	push_error ("syntax error: unary operator with no operand");
-	clear_stack (&tmp_stack);
-	return (FALSE);
-      }
-
-      ONE_OP ("M", M_unary);
-      ONE_OP ("V", V_unary);
-      ONE_OP ("S", S_unary);
-
-      /* there are no valid unary string operators */
-      if (!strncasecmp (&stack[i - 1].type, "W", 1)) {
-	push_error ("syntax error: no valid string unary ops");
-	clear_stack (&tmp_stack);
-	return (FALSE);
-      }
-
-      move_stack (&stack[i-1], &tmp_stack);
-      delete_stack (&stack[i], 1);
-      for (j = i + 1; j < *Nstack; j++) {
-	move_stack (&stack[j - 1], &stack[j]);
-      }
-      init_stack (&tmp_stack);
-      *Nstack -= 1;
-      i -= 1;
-      continue;
-    } 
+    }
   }
   clear_stack (&tmp_stack);
@@ -160,5 +273,5 @@
 
   for (i = 0; i < Nstack; i++) {
-    if (IsBufferPtr (stack[i].buffer) && (stack[i].type == 'm')) {
+    if (IsBufferPtr (stack[i].buffer) && (stack[i].type == ST_MATRIX_TMP)) {
       if (VERBOSE) gprint (GP_ERR, "free %s (buff) (%lx)\n", stack[i].name, (long) stack[i].buffer);
       free (stack[i].buffer[0].header.buffer);
@@ -167,5 +280,5 @@
       stack[i].buffer = NULL;
     }	
-    if (IsVectorPtr (stack[i].vector) && (stack[i].type == 'v')) {
+    if (IsVectorPtr (stack[i].vector) && (stack[i].type == ST_VECTOR_TMP)) {
       if (VERBOSE) gprint (GP_ERR, "free %s (vect) (%lx)\n", stack[i].name, (long) stack[i].vector);
       free (stack[i].vector[0].elements.Ptr);
@@ -193,5 +306,5 @@
 }
 
-void assign_stack (StackVar *stack, char *name, int type) {
+void assign_stack (StackVar *stack, char *name, StackVarType type) {
   stack->name = strcreate (name);
   stack->type = type;
Index: trunk/Ohana/src/opihi/lib.shell/stack_math.c
===================================================================
--- trunk/Ohana/src/opihi/lib.shell/stack_math.c	(revision 36374)
+++ trunk/Ohana/src/opihi/lib.shell/stack_math.c	(revision 36375)
@@ -6,9 +6,46 @@
 */
 
-// XXX we temporarily drop the concept of using one of the temporary input vectors for 
-// the output vector (thus saving an ALLOC): we have to juggle the size of the input vectors 
-// as well as their temporary state
-
-int VV_binary (StackVar *OUT, StackVar *V1, StackVar *V2, char *op) {
+int SSS_trinary (StackVar *OUT, StackVar *V1, StackVar *V2, StackVar *V3, char *op) {
+
+  char line[512]; // this is only used to report an error 
+  
+  // set up the possible operations : int OP int -> int, all else yield float
+  // OP is the operation performed on *M1 and *M2
+# define SSS_FUNC(OP) {						\
+    if ((V1->type == ST_SCALAR_FLT) && (V2->type == ST_SCALAR_FLT) && (V3->type == ST_SCALAR_FLT)) { \
+      opihi_flt M1  =  V1[0].FltValue;			\
+      opihi_flt M2  =  V2[0].FltValue;			\
+      opihi_flt M3  =  V3[0].FltValue;			\
+      OUT[0].type = ST_SCALAR_FLT;			\
+      OUT[0].FltValue = OP;							\
+      break;								\
+    }									\
+    if ((V1->type == ST_SCALAR_INT) && (V2->type == ST_SCALAR_INT) && (V3->type == ST_SCALAR_INT)) { \
+      opihi_int M1  =  V1[0].IntValue;			\
+      opihi_int M2  =  V2[0].IntValue;			\
+      opihi_int M3  =  V3[0].IntValue;			\
+      OUT[0].type = ST_SCALAR_INT;			\
+      OUT[0].IntValue = OP;							\
+      break;								\
+    }									\
+  }
+
+  switch (op[0]) {
+    case '?': SSS_FUNC(M1 ? M2: M3);
+    default:
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
+      push_error (line);
+      return (FALSE);
+  }
+# undef SSS_FUNC
+
+  clear_stack (V1);
+  clear_stack (V2);
+  clear_stack (V3);
+  return (TRUE);
+
+}
+
+int VVV_trinary (StackVar *OUT, StackVar *V1, StackVar *V2, StackVar *V3, char *op) {
 
   int i, Nx;
@@ -19,4 +56,7 @@
     return (FALSE);
   }
+  if (V1[0].vector[0].Nelements != V3[0].vector[0].Nelements) {
+    return (FALSE);
+  }
 
   Nx = V1[0].vector[0].Nelements;
@@ -24,5 +64,154 @@
   // create the output vector guaranteed to be temporary until the very end
   OUT[0].vector = InitVector ();
-  OUT[0].type = 'v';
+  OUT[0].type = ST_VECTOR_TMP;
+
+  // set up the possible operations : int OP int -> int, all else yield float
+  // OP is the operation performed on *M1 and *M2
+# define VVV_FUNC(OP) {						\
+    if ((V1->vector->type == OPIHI_FLT) && (V2->vector->type == OPIHI_FLT) && (V3->vector->type == OPIHI_FLT)) { \
+      CopyVector (OUT[0].vector, V1[0].vector);				\
+      opihi_flt *M1  =  V1[0].vector[0].elements.Flt;			\
+      opihi_flt *M2  =  V2[0].vector[0].elements.Flt;			\
+      opihi_flt *M3  =  V3[0].vector[0].elements.Flt;			\
+      opihi_flt *out = OUT[0].vector[0].elements.Flt;			\
+      for (i = 0; i < Nx; i++, out++, M1++, M2++, M3++) {		\
+  	*out = OP;							\
+      }									\
+      break;								\
+    }									\
+    if ((V1->vector->type == OPIHI_INT) && (V2->vector->type == OPIHI_INT) && (V3->vector->type == OPIHI_INT)) { \
+      CopyVector (OUT[0].vector, V1[0].vector);				\
+      opihi_int *M1  =  V1[0].vector[0].elements.Int;			\
+      opihi_int *M2  =  V2[0].vector[0].elements.Int;			\
+      opihi_int *M3  =  V3[0].vector[0].elements.Int;			\
+      opihi_int *out = OUT[0].vector[0].elements.Int;			\
+      for (i = 0; i < Nx; i++, out++, M1++, M2++, M3++) {		\
+  	*out = OP;							\
+      }									\
+      break;								\
+    }									\
+  }
+
+  switch (op[0]) {
+    case '?': VVV_FUNC(*M1 ? *M2: *M3);
+    default:
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
+      push_error (line);
+      return (FALSE);
+  }
+# undef VVV_FUNC
+
+  /** free up any temporary buffers: **/
+
+  if (V1[0].type == ST_VECTOR_TMP) {
+    free (V1[0].vector[0].elements.Ptr);
+    free (V1[0].vector);
+  }
+  if (V2[0].type == ST_VECTOR_TMP) {
+    free (V2[0].vector[0].elements.Ptr);
+    free (V2[0].vector);
+  }
+  if (V3[0].type == ST_VECTOR_TMP) {
+    free (V3[0].vector[0].elements.Ptr);
+    free (V3[0].vector);
+  }
+  /* at the end, V1 and V2 are deleted only if they were temporary */
+
+  clear_stack (V1);
+  clear_stack (V2);
+  clear_stack (V3);
+  return (TRUE);
+
+}
+
+int MMM_trinary (StackVar *OUT, StackVar *V1, StackVar *V2, StackVar *V3, char *op) {
+
+  int i, Nx, Ny;
+  float *out, *M1, *M2, *M3;
+  char line[512]; // this is only used to report an error 
+  
+  Nx = V1[0].buffer[0].matrix.Naxis[0];
+  Ny = V1[0].buffer[0].matrix.Naxis[1];
+
+  if (V1[0].type == ST_MATRIX_TMP) {  /** use V1 as temp buffer **/
+    OUT[0].buffer = V1[0].buffer;
+    V1[0].type = ST_MATRIX; /* prevent it from being freed below */
+  } else {
+    if (V2[0].type == ST_MATRIX_TMP) { /** use V2 as temp buffer, but header of V1 **/
+      OUT[0].buffer = V2[0].buffer;
+      V2[0].type = ST_MATRIX; /* prevent it from being freed below */
+    } else {  /* no spare temp buffer */
+      OUT[0].buffer = InitBuffer ();
+      CopyBuffer (OUT[0].buffer, V1[0].buffer);
+    }
+  }
+  OUT[0].type = ST_MATRIX_TMP; /*** <<--- says this is a temporary matrix ***/
+
+  M1  = (float *)V1[0].buffer[0].matrix.buffer;
+  M2  = (float *)V2[0].buffer[0].matrix.buffer;
+  M3  = (float *)V3[0].buffer[0].matrix.buffer;
+  out = (float *)OUT[0].buffer[0].matrix.buffer;
+
+# define MMM_FUNC(OP)					\
+  for (i = 0; i < Nx*Ny; i++, out++, M1++, M2++, M3++) {	\
+    *out = OP;						\
+  }							\
+  break; 
+
+  switch (op[0]) {
+    case '?': MMM_FUNC(*M1 ? *M2: *M3);
+    default:
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
+      push_error (line);
+      return (FALSE);
+  }
+# undef MMM_FUNC
+
+  /** free up any temporary buffers: **/
+
+  if (V1[0].type == ST_MATRIX_TMP) {
+    free (V1[0].buffer[0].header.buffer);
+    free (V1[0].buffer[0].matrix.buffer);
+    free (V1[0].buffer);
+  }
+  if (V2[0].type == ST_MATRIX_TMP) {
+    free (V2[0].buffer[0].header.buffer);
+    free (V2[0].buffer[0].matrix.buffer);
+    free (V2[0].buffer);
+  }
+  if (V3[0].type == ST_MATRIX_TMP) {
+    free (V3[0].buffer[0].header.buffer);
+    free (V3[0].buffer[0].matrix.buffer);
+    free (V3[0].buffer);
+  }
+
+  /* at the end, V1 and V2 are deleted only if they were temporary */
+
+  clear_stack (V1);
+  clear_stack (V2);
+  clear_stack (V3);
+  return (TRUE);
+
+}
+
+// XXX we temporarily drop the concept of using one of the temporary input vectors for 
+// the output vector (thus saving an ALLOC): we have to juggle the size of the input vectors 
+// as well as their temporary state
+
+int VV_binary (StackVar *OUT, StackVar *V1, StackVar *V2, char *op) {
+
+  int i, Nx;
+  char line[512]; // this is only used to report an error 
+  
+  // the vectors have to match in length
+  if (V1[0].vector[0].Nelements != V2[0].vector[0].Nelements) {
+    return (FALSE);
+  }
+
+  Nx = V1[0].vector[0].Nelements;
+
+  // create the output vector guaranteed to be temporary until the very end
+  OUT[0].vector = InitVector ();
+  OUT[0].type = ST_VECTOR_TMP;
 
   // set up the possible operations : int OP int -> int, all else yield float
@@ -59,5 +248,5 @@
       break;								\
     }									\
-    if ((FTYPE == 'S') && (V1->vector->type != OPIHI_FLT) && (V2->vector->type != OPIHI_FLT)) { \
+    if ((FTYPE == ST_SCALAR_FLT) && (V1->vector->type != OPIHI_FLT) && (V2->vector->type != OPIHI_FLT)) { \
       MatchVector (OUT[0].vector, V1[0].vector, OPIHI_FLT);		\
       opihi_int *M1  =  V1[0].vector[0].elements.Int;			\
@@ -82,25 +271,26 @@
 
   switch (op[0]) {
-    case '+': VV_FUNC('s', *M1 + *M2);
-    case '-': VV_FUNC('s', *M1 - *M2);
-    case '*': VV_FUNC('s', *M1 * *M2);
-    case '/': VV_FUNC('S', *M1 / (opihi_flt) *M2);
-    case '%': VV_FUNC('s', (long long)*M1 % (long long)*M2);
-    case '^': VV_FUNC('S', pow (*M1, *M2));
-    case '@': VV_FUNC('S', DEG_RAD*atan2 (*M1, *M2));
-    case 'D': VV_FUNC('s', MIN (*M1, *M2));
-    case 'U': VV_FUNC('s', MAX (*M1, *M2));
-    case '<': VV_FUNC('s', (*M1 < *M2) ? 1 : 0);
-    case '>': VV_FUNC('s', (*M1 > *M2) ? 1 : 0);
-    case '&': VV_FUNC('s', ((long long)*M1 & (long long)*M2));
-    case '|': VV_FUNC('s', ((long long)*M1 | (long long)*M2));
-    case 'E': VV_FUNC('s', (*M1 == *M2) ? 1 : 0);
-    case 'N': VV_FUNC('s', (*M1 != *M2) ? 1 : 0);
-    case 'L': VV_FUNC('s', (*M1 <= *M2) ? 1 : 0);
-    case 'G': VV_FUNC('s', (*M1 >= *M2) ? 1 : 0);
-    case 'A': VV_FUNC('s', (*M1 && *M2) ? 1 : 0);
-    case 'O': VV_FUNC('s', (*M1 || *M2) ? 1 : 0);
+    case '+': VV_FUNC(ST_SCALAR_INT, *M1 + *M2);
+    case '-': VV_FUNC(ST_SCALAR_INT, *M1 - *M2);
+    case '*': VV_FUNC(ST_SCALAR_INT, *M1 * *M2);
+    case '/': VV_FUNC(ST_SCALAR_FLT, *M1 / (opihi_flt) *M2);
+    case '%': VV_FUNC(ST_SCALAR_INT, (long long)*M1 % (long long)*M2);
+    case '^': VV_FUNC(ST_SCALAR_FLT, pow (*M1, *M2));
+    case '@': VV_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (*M1, *M2));
+    case 'a': VV_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (*M1, *M2));
+    case 'D': VV_FUNC(ST_SCALAR_INT, MIN (*M1, *M2));
+    case 'U': VV_FUNC(ST_SCALAR_INT, MAX (*M1, *M2));
+    case '<': VV_FUNC(ST_SCALAR_INT, (*M1 < *M2) ? 1 : 0);
+    case '>': VV_FUNC(ST_SCALAR_INT, (*M1 > *M2) ? 1 : 0);
+    case '&': VV_FUNC(ST_SCALAR_INT, ((long long)*M1 & (long long)*M2));
+    case '|': VV_FUNC(ST_SCALAR_INT, ((long long)*M1 | (long long)*M2));
+    case 'E': VV_FUNC(ST_SCALAR_INT, (*M1 == *M2) ? 1 : 0);
+    case 'N': VV_FUNC(ST_SCALAR_INT, (*M1 != *M2) ? 1 : 0);
+    case 'L': VV_FUNC(ST_SCALAR_INT, (*M1 <= *M2) ? 1 : 0);
+    case 'G': VV_FUNC(ST_SCALAR_INT, (*M1 >= *M2) ? 1 : 0);
+    case 'A': VV_FUNC(ST_SCALAR_INT, (*M1 && *M2) ? 1 : 0);
+    case 'O': VV_FUNC(ST_SCALAR_INT, (*M1 || *M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -110,9 +300,9 @@
   /** free up any temporary buffers: **/
 
-  if (V1[0].type == 'v') {
+  if (V1[0].type == ST_VECTOR_TMP) {
     free (V1[0].vector[0].elements.Ptr);
     free (V1[0].vector);
   }
-  if (V2[0].type == 'v') {
+  if (V2[0].type == ST_VECTOR_TMP) {
     free (V2[0].vector[0].elements.Ptr);
     free (V2[0].vector);
@@ -134,10 +324,10 @@
 
   OUT[0].vector = InitVector ();
-  OUT[0].type = 'v';   /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_VECTOR_TMP;   /*** <<--- says this is a temporary matrix ***/
 
   // set up the possible operations : int OP int -> int, all else yield float
   // OP is the operation performed on *M1 and *M2
 # define SV_FUNC(FTYPE,OP) {						\
-    if ((V1->type == 'S') && (V2->vector->type == OPIHI_FLT)) {		\
+    if ((V1->type == ST_SCALAR_FLT) && (V2->vector->type == OPIHI_FLT)) {		\
       CopyVector (OUT[0].vector, V2[0].vector);				\
       opihi_flt  M1  =  V1[0].FltValue;					\
@@ -149,5 +339,5 @@
       break;								\
     }									\
-    if ((V1->type == 'S') && (V2->vector->type != OPIHI_FLT)) {		\
+    if ((V1->type == ST_SCALAR_FLT) && (V2->vector->type != OPIHI_FLT)) {		\
       MatchVector (OUT[0].vector, V2[0].vector, OPIHI_FLT);		\
       opihi_flt  M1  =  V1[0].FltValue;					\
@@ -159,5 +349,5 @@
       break;								\
     }									\
-    if ((V1->type == 's') && (V2->vector->type == OPIHI_FLT)) {		\
+    if ((V1->type == ST_SCALAR_INT) && (V2->vector->type == OPIHI_FLT)) {		\
       CopyVector (OUT[0].vector, V2[0].vector);				\
       opihi_int  M1  =  V1[0].IntValue;					\
@@ -169,5 +359,5 @@
       break;								\
     }									\
-    if ((FTYPE == 'S') && (V1->type == 's') && (V2->vector->type != OPIHI_FLT)) { \
+    if ((FTYPE == ST_SCALAR_FLT) && (V1->type == ST_SCALAR_INT) && (V2->vector->type != OPIHI_FLT)) { \
       MatchVector (OUT[0].vector, V2[0].vector, OPIHI_FLT);		\
       opihi_int  M1  =  V1[0].IntValue;					\
@@ -179,5 +369,5 @@
       break;								\
     }									\
-    if ((V1->type == 's') && (V2->vector->type != OPIHI_FLT)) {		\
+    if ((V1->type == ST_SCALAR_INT) && (V2->vector->type != OPIHI_FLT)) {		\
       CopyVector (OUT[0].vector, V2[0].vector);				\
       opihi_int  M1  =  V1[0].IntValue;					\
@@ -192,25 +382,26 @@
 
   switch (op[0]) { 
-    case '+': SV_FUNC('s', M1 + *M2);
-    case '-': SV_FUNC('s', M1 - *M2);
-    case '*': SV_FUNC('s', M1 * *M2);
-    case '/': SV_FUNC('S', M1 / (opihi_flt) *M2);
-    case '%': SV_FUNC('s', (long long) M1 % (long long) *M2);
-    case '^': SV_FUNC('S', pow (M1, *M2));
-    case '@': SV_FUNC('S', DEG_RAD*atan2 (M1, *M2));
-    case 'D': SV_FUNC('s', MIN (M1, *M2));
-    case 'U': SV_FUNC('s', MAX (M1, *M2));
-    case '<': SV_FUNC('s', (M1 < *M2) ? 1 : 0);
-    case '>': SV_FUNC('s', (M1 > *M2) ? 1 : 0);
-    case '&': SV_FUNC('s', ((long long)M1 & (long long)*M2));
-    case '|': SV_FUNC('s', ((long long)M1 | (long long)*M2));
-    case 'E': SV_FUNC('s', (M1 == *M2) ? 1 : 0);
-    case 'N': SV_FUNC('s', (M1 != *M2) ? 1 : 0);
-    case 'L': SV_FUNC('s', (M1 <= *M2) ? 1 : 0);
-    case 'G': SV_FUNC('s', (M1 >= *M2) ? 1 : 0);
-    case 'A': SV_FUNC('s', (M1 && *M2) ? 1 : 0);
-    case 'O': SV_FUNC('s', (M1 || *M2) ? 1 : 0);
+    case '+': SV_FUNC(ST_SCALAR_INT, M1 + *M2);
+    case '-': SV_FUNC(ST_SCALAR_INT, M1 - *M2);
+    case '*': SV_FUNC(ST_SCALAR_INT, M1 * *M2);
+    case '/': SV_FUNC(ST_SCALAR_FLT, M1 / (opihi_flt) *M2);
+    case '%': SV_FUNC(ST_SCALAR_INT, (long long) M1 % (long long) *M2);
+    case '^': SV_FUNC(ST_SCALAR_FLT, pow (M1, *M2));
+    case '@': SV_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (M1, *M2));
+    case 'a': SV_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (M1, *M2));
+    case 'D': SV_FUNC(ST_SCALAR_INT, MIN (M1, *M2));
+    case 'U': SV_FUNC(ST_SCALAR_INT, MAX (M1, *M2));
+    case '<': SV_FUNC(ST_SCALAR_INT, (M1 < *M2) ? 1 : 0);
+    case '>': SV_FUNC(ST_SCALAR_INT, (M1 > *M2) ? 1 : 0);
+    case '&': SV_FUNC(ST_SCALAR_INT, ((long long)M1 & (long long)*M2));
+    case '|': SV_FUNC(ST_SCALAR_INT, ((long long)M1 | (long long)*M2));
+    case 'E': SV_FUNC(ST_SCALAR_INT, (M1 == *M2) ? 1 : 0);
+    case 'N': SV_FUNC(ST_SCALAR_INT, (M1 != *M2) ? 1 : 0);
+    case 'L': SV_FUNC(ST_SCALAR_INT, (M1 <= *M2) ? 1 : 0);
+    case 'G': SV_FUNC(ST_SCALAR_INT, (M1 >= *M2) ? 1 : 0);
+    case 'A': SV_FUNC(ST_SCALAR_INT, (M1 && *M2) ? 1 : 0);
+    case 'O': SV_FUNC(ST_SCALAR_INT, (M1 || *M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -219,5 +410,5 @@
 
   /** free up any temporary buffers: **/
-  if (V2[0].type == 'v') {
+  if (V2[0].type == ST_VECTOR_TMP) {
     free (V2[0].vector[0].elements.Ptr);
     free (V2[0].vector);
@@ -240,10 +431,10 @@
 
   OUT[0].vector = InitVector ();
-  OUT[0].type = 'v';   /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_VECTOR_TMP;   /*** <<--- says this is a temporary matrix ***/
 
   // set up the possible operations : int OP int -> int, all else yield float
   // OP is the operation performed on *M1 and *M2
 # define VS_FUNC(FTYPE,OP) {						\
-    if ((V2->type == 'S') && (V1->vector->type == OPIHI_FLT)) {		\
+    if ((V2->type == ST_SCALAR_FLT) && (V1->vector->type == OPIHI_FLT)) {		\
       CopyVector (OUT[0].vector, V1[0].vector);				\
       opihi_flt *M1  =  V1[0].vector[0].elements.Flt;			\
@@ -255,5 +446,5 @@
       break;								\
     }									\
-    if ((V2->type == 'S') && (V1->vector->type != OPIHI_FLT)) {		\
+    if ((V2->type == ST_SCALAR_FLT) && (V1->vector->type != OPIHI_FLT)) {		\
       MatchVector (OUT[0].vector, V1[0].vector, OPIHI_FLT);		\
       opihi_int *M1  =  V1[0].vector[0].elements.Int;			\
@@ -265,5 +456,5 @@
       break;								\
     }									\
-    if ((V2->type == 's') && (V1->vector->type == OPIHI_FLT)) {		\
+    if ((V2->type == ST_SCALAR_INT) && (V1->vector->type == OPIHI_FLT)) {		\
       CopyVector (OUT[0].vector, V1[0].vector);				\
       opihi_flt *M1  =  V1[0].vector[0].elements.Flt;			\
@@ -275,5 +466,5 @@
       break;								\
     }									\
-    if ((FTYPE == 'S') && (V2->type == 's') && (V1->vector->type != OPIHI_FLT)) { \
+    if ((FTYPE == ST_SCALAR_FLT) && (V2->type == ST_SCALAR_INT) && (V1->vector->type != OPIHI_FLT)) { \
       CopyVector (OUT[0].vector, V1[0].vector);				\
       opihi_int *M1  =  V1[0].vector[0].elements.Int;			\
@@ -285,5 +476,5 @@
       break;								\
     }									\
-    if ((V2->type == 's') && (V1->vector->type != OPIHI_FLT)) {		\
+    if ((V2->type == ST_SCALAR_INT) && (V1->vector->type != OPIHI_FLT)) {		\
       CopyVector (OUT[0].vector, V1[0].vector);				\
       opihi_int *M1  =  V1[0].vector[0].elements.Int;			\
@@ -298,25 +489,26 @@
 
   switch (op[0]) { 
-    case '+': VS_FUNC('s', *M1 + M2);
-    case '-': VS_FUNC('s', *M1 - M2);
-    case '*': VS_FUNC('s', *M1 * M2);
-    case '/': VS_FUNC('S', *M1 / (opihi_flt) M2);
-    case '%': VS_FUNC('s', (long long) *M1 % (long long) M2);
-    case '^': VS_FUNC('S', pow (*M1, M2));
-    case '@': VS_FUNC('S', DEG_RAD*atan2 (*M1, M2));
-    case 'D': VS_FUNC('s', MIN (*M1, M2));
-    case 'U': VS_FUNC('s', MAX (*M1, M2));
-    case '<': VS_FUNC('s', (*M1 < M2) ? 1 : 0);
-    case '>': VS_FUNC('s', (*M1 > M2) ? 1 : 0);
-    case '&': VS_FUNC('s', ((long long)*M1 & (long long)M2));
-    case '|': VS_FUNC('s', ((long long)*M1 | (long long)M2));
-    case 'E': VS_FUNC('s', (*M1 == M2) ? 1 : 0);
-    case 'N': VS_FUNC('s', (*M1 != M2) ? 1 : 0);
-    case 'L': VS_FUNC('s', (*M1 <= M2) ? 1 : 0);
-    case 'G': VS_FUNC('s', (*M1 >= M2) ? 1 : 0);
-    case 'A': VS_FUNC('s', (*M1 && M2) ? 1 : 0);
-    case 'O': VS_FUNC('s', (*M1 || M2) ? 1 : 0);
+    case '+': VS_FUNC(ST_SCALAR_INT, *M1 + M2);
+    case '-': VS_FUNC(ST_SCALAR_INT, *M1 - M2);
+    case '*': VS_FUNC(ST_SCALAR_INT, *M1 * M2);
+    case '/': VS_FUNC(ST_SCALAR_FLT, *M1 / (opihi_flt) M2);
+    case '%': VS_FUNC(ST_SCALAR_INT, (long long) *M1 % (long long) M2);
+    case '^': VS_FUNC(ST_SCALAR_FLT, pow (*M1, M2));
+    case '@': VS_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (*M1, M2));
+    case 'a': VS_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (*M1, M2));
+    case 'D': VS_FUNC(ST_SCALAR_INT, MIN (*M1, M2));
+    case 'U': VS_FUNC(ST_SCALAR_INT, MAX (*M1, M2));
+    case '<': VS_FUNC(ST_SCALAR_INT, (*M1 < M2) ? 1 : 0);
+    case '>': VS_FUNC(ST_SCALAR_INT, (*M1 > M2) ? 1 : 0);
+    case '&': VS_FUNC(ST_SCALAR_INT, ((long long)*M1 & (long long)M2));
+    case '|': VS_FUNC(ST_SCALAR_INT, ((long long)*M1 | (long long)M2));
+    case 'E': VS_FUNC(ST_SCALAR_INT, (*M1 == M2) ? 1 : 0);
+    case 'N': VS_FUNC(ST_SCALAR_INT, (*M1 != M2) ? 1 : 0);
+    case 'L': VS_FUNC(ST_SCALAR_INT, (*M1 <= M2) ? 1 : 0);
+    case 'G': VS_FUNC(ST_SCALAR_INT, (*M1 >= M2) ? 1 : 0);
+    case 'A': VS_FUNC(ST_SCALAR_INT, (*M1 && M2) ? 1 : 0);
+    case 'O': VS_FUNC(ST_SCALAR_INT, (*M1 || M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -326,5 +518,5 @@
   /** free up any temporary buffers: **/
 
-  if (V1[0].type == 'v') {
+  if (V1[0].type == ST_VECTOR_TMP) {
     free (V1[0].vector[0].elements.Ptr);
     free (V1[0].vector);
@@ -353,7 +545,7 @@
 
   /* if possible, use V1 as temp buffer, otherwise create new one */
-  if (V1[0].type == 'm') {  
+  if (V1[0].type == ST_MATRIX_TMP) {  
     OUT[0].buffer = V1[0].buffer;
-    V1[0].type = 'M'; /* prevent it from being freed below */
+    V1[0].type = ST_MATRIX; /* prevent it from being freed below */
   } else {  
     /* do buffer.matrix.buffer and buffer.header.buffer get correctly zeroed? */
@@ -361,5 +553,5 @@
     CopyBuffer (OUT[0].buffer, V1[0].buffer);
   }
-  OUT[0].type = 'm'; /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_MATRIX_TMP; /*** <<--- says this is a temporary matrix ***/
 
   float     *M1  = (float *) V1[0].buffer[0].matrix.buffer;
@@ -395,4 +587,5 @@
     case '^': MV_FUNC(pow (*M1, *M2));
     case '@': MV_FUNC(DEG_RAD*atan2 (*M1, *M2));
+    case 'a': MV_FUNC(DEG_RAD*atan2 (*M1, *M2));
     case 'D': MV_FUNC(MIN (*M1, *M2));
     case 'U': MV_FUNC(MAX (*M1, *M2));
@@ -408,5 +601,5 @@
     case 'O': MV_FUNC((*M1 || *M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -416,10 +609,10 @@
   /** free up any temporary buffers: **/
 
-  if (V1[0].type == 'm') {
+  if (V1[0].type == ST_MATRIX_TMP) {
     free (V1[0].buffer[0].header.buffer);
     free (V1[0].buffer[0].matrix.buffer);
     free (V1[0].buffer);
   }
-  if (V2[0].type == 'v') {
+  if (V2[0].type == ST_VECTOR_TMP) {
     free (V2[0].vector[0].elements.Ptr);
     free (V2[0].vector);
@@ -446,12 +639,12 @@
 
   /* if possible, use V2 as temp buffer, otherwise create new one */
-  if (V2[0].type == 'm') {
+  if (V2[0].type == ST_MATRIX_TMP) {
     OUT[0].buffer = V2[0].buffer;
-    V2[0].type = 'M'; /* prevent it from being freed below */
+    V2[0].type = ST_MATRIX; /* prevent it from being freed below */
   } else {  /* no spare temp buffer */
     OUT[0].buffer = InitBuffer ();
     CopyBuffer (OUT[0].buffer, V2[0].buffer);
   }
-  OUT[0].type = 'm'; /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_MATRIX_TMP; /*** <<--- says this is a temporary matrix ***/
 
   float     *M2  = (float *) V2[0].buffer[0].matrix.buffer;
@@ -487,4 +680,5 @@
     case '^': VM_FUNC(pow (*M1, *M2));
     case '@': VM_FUNC(DEG_RAD*atan2 (*M1, *M2));
+    case 'a': VM_FUNC(DEG_RAD*atan2 (*M1, *M2));
     case 'D': VM_FUNC(MIN (*M1, *M2));
     case 'U': VM_FUNC(MAX (*M1, *M2));
@@ -500,5 +694,5 @@
     case 'O': VM_FUNC((*M1 || *M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -508,9 +702,9 @@
   /** free up any temporary buffers: **/
 
-  if (V1[0].type == 'v') {
+  if (V1[0].type == ST_VECTOR_TMP) {
     free (V1[0].vector[0].elements.Ptr);
     free (V1[0].vector);
   }
-  if (V2[0].type == 'm') {
+  if (V2[0].type == ST_MATRIX_TMP) {
     free (V2[0].buffer[0].header.buffer);
     free (V2[0].buffer[0].matrix.buffer);
@@ -535,11 +729,11 @@
   Ny = V1[0].buffer[0].matrix.Naxis[1];
 
-  if (V1[0].type == 'm') {  /** use V1 as temp buffer **/
+  if (V1[0].type == ST_MATRIX_TMP) {  /** use V1 as temp buffer **/
     OUT[0].buffer = V1[0].buffer;
-    V1[0].type = 'M'; /* prevent it from being freed below */
+    V1[0].type = ST_MATRIX; /* prevent it from being freed below */
   } else {
-    if (V2[0].type == 'm') { /** use V2 as temp buffer, but header of V1 **/
+    if (V2[0].type == ST_MATRIX_TMP) { /** use V2 as temp buffer, but header of V1 **/
       OUT[0].buffer = V2[0].buffer;
-      V2[0].type = 'M'; /* prevent it from being freed below */
+      V2[0].type = ST_MATRIX; /* prevent it from being freed below */
     } else {  /* no spare temp buffer */
       OUT[0].buffer = InitBuffer ();
@@ -547,5 +741,5 @@
     }
   }
-  OUT[0].type = 'm'; /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_MATRIX_TMP; /*** <<--- says this is a temporary matrix ***/
 
   M1  = (float *)V1[0].buffer[0].matrix.buffer;
@@ -567,4 +761,5 @@
     case '^': MM_FUNC(pow (*M1, *M2));
     case '@': MM_FUNC(DEG_RAD*atan2 (*M1, *M2));
+    case 'a': MM_FUNC(DEG_RAD*atan2 (*M1, *M2));
     case 'D': MM_FUNC(MIN (*M1, *M2));
     case 'U': MM_FUNC(MAX (*M1, *M2));
@@ -580,5 +775,5 @@
     case 'O': MM_FUNC((*M1 || *M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -588,10 +783,10 @@
   /** free up any temporary buffers: **/
 
-  if (V1[0].type == 'm') {
+  if (V1[0].type == ST_MATRIX_TMP) {
     free (V1[0].buffer[0].header.buffer);
     free (V1[0].buffer[0].matrix.buffer);
     free (V1[0].buffer);
   }
-  if (V2[0].type == 'm') {
+  if (V2[0].type == ST_MATRIX_TMP) {
     free (V2[0].buffer[0].header.buffer);
     free (V2[0].buffer[0].matrix.buffer);
@@ -615,12 +810,12 @@
 
   /* if possible, use V1 as temp buffer, otherwise create new one */
-  if (V1[0].type == 'm') {
+  if (V1[0].type == ST_MATRIX_TMP) {
     OUT[0].buffer = V1[0].buffer;
-    V1[0].type = 'M'; /* prevent it from being freed below */
+    V1[0].type = ST_MATRIX; /* prevent it from being freed below */
   } else {
     OUT[0].buffer = InitBuffer ();
     CopyBuffer (OUT[0].buffer, V1[0].buffer);
   }
-  OUT[0].type = 'm';      /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_MATRIX_TMP;      /*** <<--- says this is a temporary matrix ***/
 
   float *M1    = (float *)V1[0].buffer[0].matrix.buffer;
@@ -628,5 +823,5 @@
 
 # define MS_FUNC(OP) {					\
-    if (V2->type == 'S')  {				\
+    if (V2->type == ST_SCALAR_FLT)  {				\
       opihi_flt M2 = V2[0].FltValue;			\
       for (i = 0; i < Nx*Ny; i++, out++, M1++) {	\
@@ -635,5 +830,5 @@
       break;						\
     }							\
-    if (V2->type == 's')  {				\
+    if (V2->type == ST_SCALAR_INT)  {				\
       opihi_int M2 = V2[0].IntValue;			\
       for (i = 0; i < Nx*Ny; i++, out++, M1++) {	\
@@ -652,4 +847,5 @@
     case '^': MS_FUNC(pow (*M1, M2));
     case '@': MS_FUNC(DEG_RAD*atan2 (*M1, M2));
+    case 'a': MS_FUNC(DEG_RAD*atan2 (*M1, M2));
     case 'D': MS_FUNC(MIN (*M1, M2));
     case 'U': MS_FUNC(MAX (*M1, M2));
@@ -665,5 +861,5 @@
     case 'O': MS_FUNC((*M1 || M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -671,5 +867,5 @@
 # undef MS_FUNC
 
-  if (V1[0].type == 'm') {
+  if (V1[0].type == ST_MATRIX_TMP) {
     free (V1[0].buffer[0].header.buffer);
     free (V1[0].buffer[0].matrix.buffer);
@@ -691,12 +887,12 @@
   Ny = V2[0].buffer[0].matrix.Naxis[1];
 
-  if (V2[0].type == 'm') {  /* V2[0] is NOT temporary, we can't use it for storage */
+  if (V2[0].type == ST_MATRIX_TMP) {  /* V2[0] is NOT temporary, we can't use it for storage */
     OUT[0].buffer = V2[0].buffer;
-    V2[0].type = 'M'; /* prevent it from being freed below */
+    V2[0].type = ST_MATRIX; /* prevent it from being freed below */
   } else {
     OUT[0].buffer = InitBuffer ();
     CopyBuffer (OUT[0].buffer, V2[0].buffer);
   }
-  OUT[0].type = 'm'; /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_MATRIX_TMP; /*** <<--- says this is a temporary matrix ***/
 
   float *M2    = (float *)V2[0].buffer[0].matrix.buffer;
@@ -704,5 +900,5 @@
 
 # define SM_FUNC(OP) {					\
-    if (V1->type == 'S')  {				\
+    if (V1->type == ST_SCALAR_FLT)  {				\
       opihi_flt M1 = V1[0].FltValue;			\
       for (i = 0; i < Nx*Ny; i++, out++, M2++) {	\
@@ -711,5 +907,5 @@
       break;						\
     }							\
-    if (V1->type == 's')  {				\
+    if (V1->type == ST_SCALAR_INT)  {				\
       opihi_int M1 = V1[0].IntValue;			\
       for (i = 0; i < Nx*Ny; i++, out++, M2++) {	\
@@ -728,4 +924,5 @@
     case '^': SM_FUNC(pow (M1, *M2));
     case '@': SM_FUNC(DEG_RAD*atan2 (M1, *M2));
+    case 'a': SM_FUNC(DEG_RAD*atan2 (M1, *M2));
     case 'D': SM_FUNC(MIN (M1, *M2));
     case 'U': SM_FUNC(MAX (M1, *M2));
@@ -741,5 +938,5 @@
     case 'O': SM_FUNC((M1 || *M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -747,5 +944,5 @@
 # undef SM_FUNC
 
-  if (V2[0].type == 'm') {
+  if (V2[0].type == ST_MATRIX_TMP) {
     free (V2[0].buffer[0].header.buffer);
     free (V2[0].buffer[0].matrix.buffer);
@@ -764,36 +961,36 @@
 
 # define SS_FUNC(FTYPE,OP) {						\
-    if ((V1->type == 'S') && (V2->type == 'S')) {			\
+    if ((V1->type == ST_SCALAR_FLT) && (V2->type == ST_SCALAR_FLT)) {			\
       opihi_flt M1 = V1[0].FltValue;					\
       opihi_flt M2 = V2[0].FltValue;					\
-      OUT[0].type = 'S';						\
+      OUT[0].type = ST_SCALAR_FLT;						\
       OUT[0].FltValue = OP;						\
       break;								\
     }									\
-    if ((V1->type == 'S') && (V2->type == 's')) {			\
+    if ((V1->type == ST_SCALAR_FLT) && (V2->type == ST_SCALAR_INT)) {			\
       opihi_flt M1 = V1[0].FltValue;					\
       opihi_int M2 = V2[0].IntValue;					\
-      OUT[0].type = 'S';						\
+      OUT[0].type = ST_SCALAR_FLT;						\
       OUT[0].FltValue = OP;						\
       break;								\
     }									\
-    if ((V1->type == 's') && (V2->type == 'S')) {			\
+    if ((V1->type == ST_SCALAR_INT) && (V2->type == ST_SCALAR_FLT)) {			\
       opihi_int M1 = V1[0].IntValue;					\
       opihi_flt M2 = V2[0].FltValue;					\
-      OUT[0].type = 'S';						\
+      OUT[0].type = ST_SCALAR_FLT;						\
       OUT[0].FltValue = OP;						\
       break;								\
     }									\
-    if ((FTYPE == 'S') && (V1->type == 's') && (V2->type == 's')) {	\
+    if ((FTYPE == ST_SCALAR_FLT) && (V1->type == ST_SCALAR_INT) && (V2->type == ST_SCALAR_INT)) {	\
       opihi_int M1 = V1[0].IntValue;					\
       opihi_int M2 = V2[0].IntValue;					\
-      OUT[0].type = 'S';						\
+      OUT[0].type = ST_SCALAR_FLT;						\
       OUT[0].FltValue = OP;						\
       break;								\
     }									\
-    if ((V1->type == 's') && (V2->type == 's')) {			\
+    if ((V1->type == ST_SCALAR_INT) && (V2->type == ST_SCALAR_INT)) {			\
       opihi_int M1 = V1[0].IntValue;					\
       opihi_int M2 = V2[0].IntValue;					\
-      OUT[0].type = 's';						\
+      OUT[0].type = ST_SCALAR_INT;						\
       OUT[0].IntValue = OP;						\
       break;								\
@@ -802,25 +999,26 @@
 
   switch (op[0]) { 
-    case '+': SS_FUNC('s', M1 + M2);
-    case '-': SS_FUNC('s', M1 - M2);
-    case '*': SS_FUNC('s', M1 * M2);
-    case '/': SS_FUNC('S', M1 / (opihi_flt) M2);
-    case '%': SS_FUNC('s', (long long) M1 % (long long) M2);
-    case '^': SS_FUNC('S', pow (M1, M2));
-    case '@': SS_FUNC('S', DEG_RAD*atan2 (M1, M2));
-    case 'D': SS_FUNC('s', MIN (M1, M2));
-    case 'U': SS_FUNC('s', MAX (M1, M2));
-    case '<': SS_FUNC('s', (M1 < M2) ? 1 : 0);
-    case '>': SS_FUNC('s', (M1 > M2) ? 1 : 0);
-    case '&': SS_FUNC('s', ((long long)M1 & (long long)M2));
-    case '|': SS_FUNC('s', ((long long)M1 | (long long)M2));
-    case 'E': SS_FUNC('s', (M1 == M2) ? 1 : 0);
-    case 'N': SS_FUNC('s', (M1 != M2) ? 1 : 0);
-    case 'L': SS_FUNC('s', (M1 <= M2) ? 1 : 0);
-    case 'G': SS_FUNC('s', (M1 >= M2) ? 1 : 0);
-    case 'A': SS_FUNC('s', (M1 && M2) ? 1 : 0);
-    case 'O': SS_FUNC('s', (M1 || M2) ? 1 : 0);
+    case '+': SS_FUNC(ST_SCALAR_INT, M1 + M2);
+    case '-': SS_FUNC(ST_SCALAR_INT, M1 - M2);
+    case '*': SS_FUNC(ST_SCALAR_INT, M1 * M2);
+    case '/': SS_FUNC(ST_SCALAR_FLT, M1 / (opihi_flt) M2);
+    case '%': SS_FUNC(ST_SCALAR_INT, (long long) M1 % (long long) M2);
+    case '^': SS_FUNC(ST_SCALAR_FLT, pow (M1, M2));
+    case '@': SS_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (M1, M2));
+    case 'a': SS_FUNC(ST_SCALAR_FLT, DEG_RAD*atan2 (M1, M2));
+    case 'D': SS_FUNC(ST_SCALAR_INT, MIN (M1, M2));
+    case 'U': SS_FUNC(ST_SCALAR_INT, MAX (M1, M2));
+    case '<': SS_FUNC(ST_SCALAR_INT, (M1 < M2) ? 1 : 0);
+    case '>': SS_FUNC(ST_SCALAR_INT, (M1 > M2) ? 1 : 0);
+    case '&': SS_FUNC(ST_SCALAR_INT, ((long long)M1 & (long long)M2));
+    case '|': SS_FUNC(ST_SCALAR_INT, ((long long)M1 | (long long)M2));
+    case 'E': SS_FUNC(ST_SCALAR_INT, (M1 == M2) ? 1 : 0);
+    case 'N': SS_FUNC(ST_SCALAR_INT, (M1 != M2) ? 1 : 0);
+    case 'L': SS_FUNC(ST_SCALAR_INT, (M1 <= M2) ? 1 : 0);
+    case 'G': SS_FUNC(ST_SCALAR_INT, (M1 >= M2) ? 1 : 0);
+    case 'A': SS_FUNC(ST_SCALAR_INT, (M1 && M2) ? 1 : 0);
+    case 'O': SS_FUNC(ST_SCALAR_INT, (M1 || M2) ? 1 : 0);
     default:
-      sprintf (line, "error: op %c not defined!", op[0]);
+      snprintf (line, 512, "error: op %c not defined!", op[0]);
       push_error (line);
       return (FALSE);
@@ -844,5 +1042,5 @@
 
   if ((op[0] != 'N') && (op[0] != 'E')) {
-    sprintf (line, "error: op %c not defined for string operations!", op[0]);
+    snprintf (line, 512, "error: op %c not defined for string operations!", op[0]);
     push_error (line);
     return (FALSE);
@@ -853,9 +1051,17 @@
      thus: string == number -> false */
 
-  if (!strncasecmp (&V1[0].type, "S", 1)) {
+  if (V1[0].type == ST_SCALAR_INT) {
     value = (op[0] == 'N');
     goto escape;
   }
-  if (!strncasecmp (&V2[0].type, "S", 1)) {
+  if (V1[0].type == ST_SCALAR_FLT) {
+    value = (op[0] == 'N');
+    goto escape;
+  }
+  if (V2[0].type == ST_SCALAR_INT) {
+    value = (op[0] == 'N');
+    goto escape;
+  }
+  if (V2[0].type == ST_SCALAR_FLT) {
     value = (op[0] == 'N');
     goto escape;
@@ -870,5 +1076,5 @@
     break; 
     default:
-      sprintf (line, "error: op %c not defined for string operations!", op[0]);
+      snprintf (line, 512, "error: op %c not defined for string operations!", op[0]);
       push_error (line);
       return (FALSE);
@@ -877,5 +1083,5 @@
 escape:
   OUT[0].FltValue = value;
-  OUT[0].type = 'S';
+  OUT[0].type = ST_SCALAR_FLT;
 
   clear_stack (V1);
@@ -891,21 +1097,21 @@
   
 # define S_FUNC(OP,FTYPE) {			\
-    if (V1->type == 'S') {			\
+    if (V1->type == ST_SCALAR_FLT) {			\
       opihi_flt M1  = V1[0].FltValue;		\
-      OUT[0].type = 'S';			\
+      OUT[0].type = ST_SCALAR_FLT;			\
       OUT[0].FltValue = OP;			\
       clear_stack (V1);				\
       return (TRUE);				\
     }						\
-    if ((FTYPE == 'S') && (V1->type == 's')) {	\
+    if ((FTYPE == ST_SCALAR_FLT) && (V1->type == ST_SCALAR_INT)) {	\
       opihi_int M1  = V1[0].IntValue;		\
-      OUT[0].type = 'S';			\
+      OUT[0].type = ST_SCALAR_FLT;			\
       OUT[0].FltValue = OP;			\
       clear_stack (V1);				\
       return (TRUE);				\
     }						\
-    if ((FTYPE == 's') && (V1->type == 's')) {	\
+    if ((FTYPE == ST_SCALAR_INT) && (V1->type == ST_SCALAR_INT)) {	\
       opihi_int M1  = V1[0].IntValue;		\
-      OUT[0].type = 's';			\
+      OUT[0].type = ST_SCALAR_INT;			\
       OUT[0].IntValue = OP;			\
       clear_stack (V1);				\
@@ -914,40 +1120,40 @@
   }
 
-  if (!strcmp (op, "="))      S_FUNC(M1, 's');
-  if (!strcmp (op, "abs"))    S_FUNC(fabs(M1), 's');
-  if (!strcmp (op, "int"))    S_FUNC((long long)(M1), 's');
-  if (!strcmp (op, "exp"))    S_FUNC(exp (M1), 'S');
-  if (!strcmp (op, "ten"))    S_FUNC(pow (10.0,M1), 'S');
-  if (!strcmp (op, "log"))    S_FUNC(log10 (M1), 'S');
-  if (!strcmp (op, "ln"))     S_FUNC(log (M1), 'S');
-  if (!strcmp (op, "sqrt"))   S_FUNC(sqrt (M1), 'S');
-  if (!strcmp (op, "erf"))    S_FUNC(erf (M1), 'S');
-  if (!strcmp (op, "sinh"))   S_FUNC(sinh (M1), 'S');
-  if (!strcmp (op, "cosh"))   S_FUNC(cosh (M1), 'S');
-  if (!strcmp (op, "asinh"))  S_FUNC(asinh (M1), 'S');
-  if (!strcmp (op, "acosh"))  S_FUNC(acosh (M1), 'S');
-  if (!strcmp (op, "lgamma")) S_FUNC(lgamma (M1), 'S');
-  if (!strcmp (op, "sin"))    S_FUNC(sin (M1), 'S');
-  if (!strcmp (op, "cos"))    S_FUNC(cos (M1), 'S');
-  if (!strcmp (op, "tan"))    S_FUNC(tan (M1), 'S');
-  if (!strcmp (op, "dsin"))   S_FUNC(sin (M1*RAD_DEG), 'S');
-  if (!strcmp (op, "dcos"))   S_FUNC(cos (M1*RAD_DEG), 'S');
-  if (!strcmp (op, "dtan"))   S_FUNC(tan (M1*RAD_DEG), 'S');
-  if (!strcmp (op, "asin"))   S_FUNC(asin (M1), 'S');
-  if (!strcmp (op, "acos"))   S_FUNC(acos (M1), 'S');
-  if (!strcmp (op, "atan"))   S_FUNC(atan (M1), 'S');
-  if (!strcmp (op, "dasin"))  S_FUNC(asin (M1)*DEG_RAD, 'S');
-  if (!strcmp (op, "dacos"))  S_FUNC(acos (M1)*DEG_RAD, 'S');
-  if (!strcmp (op, "datan"))  S_FUNC(atan (M1)*DEG_RAD, 'S');
-  if (!strcmp (op, "rnd"))    S_FUNC(M1*0.0 + drand48(), 'S');
-  if (!strcmp (op, "not"))    S_FUNC(!(M1), 's');
-  if (!strcmp (op, "--"))     S_FUNC(-1*M1, 's'); // NOTE: opihi_int is signed, 
-  if (!strcmp (op, "isinf"))  S_FUNC(!finite(M1), 'S'); // XXX modify in future 
-  if (!strcmp (op, "isnan"))  S_FUNC(isnan(M1), 'S'); // XXX modify in future   
+  if (!strcmp (op, "="))      S_FUNC(M1, ST_SCALAR_INT);
+  if (!strcmp (op, "abs"))    S_FUNC(fabs(M1), ST_SCALAR_INT);
+  if (!strcmp (op, "int"))    S_FUNC((long long)(M1), ST_SCALAR_INT);
+  if (!strcmp (op, "exp"))    S_FUNC(exp (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "ten"))    S_FUNC(pow (10.0,M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "log"))    S_FUNC(log10 (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "ln"))     S_FUNC(log (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "sqrt"))   S_FUNC(sqrt (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "erf"))    S_FUNC(erf (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "sinh"))   S_FUNC(sinh (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "cosh"))   S_FUNC(cosh (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "asinh"))  S_FUNC(asinh (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "acosh"))  S_FUNC(acosh (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "lgamma")) S_FUNC(lgamma (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "sin"))    S_FUNC(sin (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "cos"))    S_FUNC(cos (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "tan"))    S_FUNC(tan (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "dsin"))   S_FUNC(sin (M1*RAD_DEG), ST_SCALAR_FLT);
+  if (!strcmp (op, "dcos"))   S_FUNC(cos (M1*RAD_DEG), ST_SCALAR_FLT);
+  if (!strcmp (op, "dtan"))   S_FUNC(tan (M1*RAD_DEG), ST_SCALAR_FLT);
+  if (!strcmp (op, "asin"))   S_FUNC(asin (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "acos"))   S_FUNC(acos (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "atan"))   S_FUNC(atan (M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "dasin"))  S_FUNC(asin (M1)*DEG_RAD, ST_SCALAR_FLT);
+  if (!strcmp (op, "dacos"))  S_FUNC(acos (M1)*DEG_RAD, ST_SCALAR_FLT);
+  if (!strcmp (op, "datan"))  S_FUNC(atan (M1)*DEG_RAD, ST_SCALAR_FLT);
+  if (!strcmp (op, "rnd"))    S_FUNC(M1*0.0 + drand48(), ST_SCALAR_FLT);
+  if (!strcmp (op, "not"))    S_FUNC(!(M1), ST_SCALAR_INT);
+  if (!strcmp (op, "--"))     S_FUNC(-1*M1, ST_SCALAR_INT); // NOTE: opihi_int is signed, 
+  if (!strcmp (op, "isinf"))  S_FUNC(!finite(M1), ST_SCALAR_FLT); // XXX modify in future 
+  if (!strcmp (op, "isnan"))  S_FUNC(isnan(M1), ST_SCALAR_FLT); // XXX modify in future   
 
 # undef S_FUNC
 
   clear_stack (V1);
-  sprintf (line, "error: op %s not defined!", op);
+  snprintf (line, 512, "error: op %s not defined!", op);
   push_error (line);
   return (FALSE);
@@ -962,5 +1168,5 @@
 
   OUT[0].vector = InitVector ();
-  OUT[0].type = 'v'; /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_VECTOR_TMP; /*** <<--- says this is a temporary matrix ***/
 
 # define V_FUNC(OP,FTYPE) {					\
@@ -974,5 +1180,5 @@
       goto escape;						\
     }								\
-    if ((V1->vector->type == OPIHI_INT) && (FTYPE == 'S')) {	\
+    if ((V1->vector->type == OPIHI_INT) && (FTYPE == ST_SCALAR_FLT)) {	\
       MatchVector (OUT[0].vector, V1[0].vector, OPIHI_FLT);	\
       opihi_int *M1  = V1[0].vector[0].elements.Int;		\
@@ -983,5 +1189,5 @@
       goto escape;						\
     }								\
-    if ((V1->vector->type == OPIHI_INT) && (FTYPE == 's')) {	\
+    if ((V1->vector->type == OPIHI_INT) && (FTYPE == ST_SCALAR_INT)) {	\
       CopyVector (OUT[0].vector, V1[0].vector);			\
       opihi_int *M1  = V1[0].vector[0].elements.Int;		\
@@ -993,39 +1199,39 @@
     } }							
 
-  if (!strcmp (op, "="))      V_FUNC(*M1, 's');
-  if (!strcmp (op, "abs"))    V_FUNC(fabs(*M1), 's');
-  if (!strcmp (op, "int"))    V_FUNC((long long)(*M1), 's');
-  if (!strcmp (op, "exp"))    V_FUNC(exp(*M1), 'S');
-  if (!strcmp (op, "ten"))    V_FUNC(pow(10.0,*M1), 'S');
-  if (!strcmp (op, "log"))    V_FUNC(log10(*M1), 'S');
-  if (!strcmp (op, "ln"))     V_FUNC(log(*M1), 'S');
-  if (!strcmp (op, "sqrt"))   V_FUNC(sqrt(*M1), 'S');
-  if (!strcmp (op, "erf"))    V_FUNC(erf(*M1), 'S');
-  if (!strcmp (op, "sinh"))   V_FUNC(sinh(*M1), 'S');
-  if (!strcmp (op, "cosh"))   V_FUNC(cosh(*M1), 'S');
-  if (!strcmp (op, "asinh"))  V_FUNC(asinh(*M1), 'S');
-  if (!strcmp (op, "acosh"))  V_FUNC(acosh(*M1), 'S');
-  if (!strcmp (op, "lgamma")) V_FUNC(lgamma(*M1), 'S');
-  if (!strcmp (op, "sin"))    V_FUNC(sin(*M1), 'S');
-  if (!strcmp (op, "cos"))    V_FUNC(cos(*M1), 'S');
-  if (!strcmp (op, "tan"))    V_FUNC(tan(*M1), 'S');
-  if (!strcmp (op, "dsin"))   V_FUNC(sin(*M1*RAD_DEG), 'S');
-  if (!strcmp (op, "dcos"))   V_FUNC(cos(*M1*RAD_DEG), 'S');
-  if (!strcmp (op, "dtan"))   V_FUNC(tan(*M1*RAD_DEG), 'S');
-  if (!strcmp (op, "asin"))   V_FUNC(asin(*M1), 'S');
-  if (!strcmp (op, "acos"))   V_FUNC(acos(*M1), 'S');
-  if (!strcmp (op, "atan"))   V_FUNC(atan(*M1), 'S');
-  if (!strcmp (op, "dasin"))  V_FUNC(asin(*M1)*DEG_RAD, 'S');
-  if (!strcmp (op, "dacos"))  V_FUNC(acos(*M1)*DEG_RAD, 'S');
-  if (!strcmp (op, "datan"))  V_FUNC(atan(*M1)*DEG_RAD, 'S');
-  if (!strcmp (op, "rnd"))    V_FUNC(drand48(), 'S');
-  if (!strcmp (op, "ramp"))   V_FUNC(i, 's');
-  if (!strcmp (op, "zero"))   V_FUNC(0, 's');
-  if (!strcmp (op, "not"))    V_FUNC(!(*M1), 's');
-  if (!strcmp (op, "--"))     V_FUNC(-1*(*M1), 's'); // NOTE: opihi_int is signed
-  if (!strcmp (op, "isinf"))  V_FUNC(!finite(*M1), 'S');
-  if (!strcmp (op, "isnan"))  V_FUNC(isnan(*M1), 'S');
-  if (!strcmp (op, "xramp"))  V_FUNC(i, 's');
-  if (!strcmp (op, "yramp"))  V_FUNC(0, 's');
+  if (!strcmp (op, "="))      V_FUNC(*M1, ST_SCALAR_INT);
+  if (!strcmp (op, "abs"))    V_FUNC(fabs(*M1), ST_SCALAR_INT);
+  if (!strcmp (op, "int"))    V_FUNC((long long)(*M1), ST_SCALAR_INT);
+  if (!strcmp (op, "exp"))    V_FUNC(exp(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "ten"))    V_FUNC(pow(10.0,*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "log"))    V_FUNC(log10(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "ln"))     V_FUNC(log(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "sqrt"))   V_FUNC(sqrt(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "erf"))    V_FUNC(erf(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "sinh"))   V_FUNC(sinh(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "cosh"))   V_FUNC(cosh(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "asinh"))  V_FUNC(asinh(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "acosh"))  V_FUNC(acosh(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "lgamma")) V_FUNC(lgamma(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "sin"))    V_FUNC(sin(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "cos"))    V_FUNC(cos(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "tan"))    V_FUNC(tan(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "dsin"))   V_FUNC(sin(*M1*RAD_DEG), ST_SCALAR_FLT);
+  if (!strcmp (op, "dcos"))   V_FUNC(cos(*M1*RAD_DEG), ST_SCALAR_FLT);
+  if (!strcmp (op, "dtan"))   V_FUNC(tan(*M1*RAD_DEG), ST_SCALAR_FLT);
+  if (!strcmp (op, "asin"))   V_FUNC(asin(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "acos"))   V_FUNC(acos(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "atan"))   V_FUNC(atan(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "dasin"))  V_FUNC(asin(*M1)*DEG_RAD, ST_SCALAR_FLT);
+  if (!strcmp (op, "dacos"))  V_FUNC(acos(*M1)*DEG_RAD, ST_SCALAR_FLT);
+  if (!strcmp (op, "datan"))  V_FUNC(atan(*M1)*DEG_RAD, ST_SCALAR_FLT);
+  if (!strcmp (op, "rnd"))    V_FUNC(drand48(), ST_SCALAR_FLT);
+  if (!strcmp (op, "ramp"))   V_FUNC(i, ST_SCALAR_INT);
+  if (!strcmp (op, "zero"))   V_FUNC(0, ST_SCALAR_INT);
+  if (!strcmp (op, "not"))    V_FUNC(!(*M1), ST_SCALAR_INT);
+  if (!strcmp (op, "--"))     V_FUNC(-1*(*M1), ST_SCALAR_INT); // NOTE: opihi_int is signed
+  if (!strcmp (op, "isinf"))  V_FUNC(!finite(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "isnan"))  V_FUNC(isnan(*M1), ST_SCALAR_FLT);
+  if (!strcmp (op, "xramp"))  V_FUNC(i, ST_SCALAR_INT);
+  if (!strcmp (op, "yramp"))  V_FUNC(0, ST_SCALAR_INT);
   /* xramp and yramp above only make sense for matrices. for vectors, xramp = ramp, yramp = zero */
 
@@ -1034,5 +1240,5 @@
 escape:
 
-  if (V1[0].type == 'v') {
+  if (V1[0].type == ST_VECTOR_TMP) {
     free (V1[0].vector[0].elements.Ptr);
     free (V1[0].vector);
@@ -1053,12 +1259,12 @@
   Ny = V1[0].buffer[0].matrix.Naxis[1];
 
-  if (V1[0].type == 'm') {
+  if (V1[0].type == ST_MATRIX_TMP) {
     OUT[0].buffer = V1[0].buffer;
-    V1[0].type = 'M'; /* prevent it from being freed below */
+    V1[0].type = ST_MATRIX; /* prevent it from being freed below */
   } else {
     OUT[0].buffer = InitBuffer ();
     CopyBuffer (OUT[0].buffer, V1[0].buffer);
   }
-  OUT[0].type = 'm';      /*** <<--- says this is a temporary matrix ***/
+  OUT[0].type = ST_MATRIX_TMP;      /*** <<--- says this is a temporary matrix ***/
   M1  = (float *) V1[0].buffer[0].matrix.buffer;
   out = (float *)OUT[0].buffer[0].matrix.buffer;
@@ -1116,5 +1322,5 @@
   }
   
-  if (V1[0].type == 'm') {
+  if (V1[0].type == ST_MATRIX_TMP) {
     free (V1[0].buffer[0].header.buffer);
     free (V1[0].buffer[0].matrix.buffer);
Index: trunk/Ohana/src/opihi/pantasks/CheckController.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/CheckController.c	(revision 36374)
+++ trunk/Ohana/src/opihi/pantasks/CheckController.c	(revision 36375)
@@ -29,10 +29,23 @@
   if (p != NULL) goto escape;
 
+  /** skip past any leading garbage? ***/
+
+  /** find "Njobs: **/
+  p = memstr (buffer.buffer, "BEGIN BLOCK Njobs:", MIN(buffer.Nbuffer, 256));
+  p += strlen("BEGIN BLOCK Njobs:");
+
   /** parse job list **/
-  status = sscanf (buffer.buffer, "%*s %d", &Njobs);
+  status = sscanf (p, "%d", &Njobs);
   if (status != 1) goto escape;
   if (VerboseMode()) gprint (GP_ERR, "parse %d jobs on stack %f\n", Njobs, TimerElapsed(&start, TRUE));
 
-  p = buffer.buffer;
+  /* output looks like:
+     BEGIN BLOCK Njobs: NN\n
+     ID name machine\n
+     ID name machine\n
+  */
+
+  // p is currently pointing at "BEGIN BLOCK Njobs"
+
   for (i = 0; i < Njobs; i++) {
     q = strchr (p, '\n');
@@ -67,10 +80,23 @@
   if (p != NULL) goto escape;
 
+  /** skip past any leading garbage? ***/
+
+  /** find "Njobs: **/
+  p = memstr (buffer.buffer, "BEGIN BLOCK Njobs:", MIN(buffer.Nbuffer, 256));
+  p += strlen("BEGIN BLOCK Njobs:");
+
   /** parse job list **/
-  status = sscanf (buffer.buffer, "%*s %d", &Njobs);
+  status = sscanf (p, "%d", &Njobs);
   if (status != 1) goto escape;
   if (VerboseMode()) gprint (GP_ERR, "check crash stack %f\n", TimerElapsed(&start, TRUE)); 
 
-  p = buffer.buffer;
+  /* output looks like:
+     BEGIN BLOCK Njobs: NN\n
+     ID name machine\n
+     ID name machine\n
+  */
+
+  // p is currently pointing at "BEGIN BLOCK Njobs"
+
   for (i = 0; i < Njobs; i++) {
     q = strchr (p, '\n');
Index: trunk/Ohana/src/opihi/pcontrol/JobOps.c
===================================================================
--- trunk/Ohana/src/opihi/pcontrol/JobOps.c	(revision 36374)
+++ trunk/Ohana/src/opihi/pcontrol/JobOps.c	(revision 36375)
@@ -59,4 +59,16 @@
   pcontrol_exit (53);
   return (NULL);
+}
+
+int GetJobStackIDbyName (char *name) {
+  if (!strcasecmp (name, "ALLJOBS"))  return PCONTROL_JOB_ALLJOBS;
+  if (!strcasecmp (name, "PENDING"))  return PCONTROL_JOB_PENDING;
+  if (!strcasecmp (name, "BUSY"))     return PCONTROL_JOB_BUSY   ;
+  if (!strcasecmp (name, "RESP"))     return PCONTROL_JOB_RESP   ;
+  if (!strcasecmp (name, "DONE"))     return PCONTROL_JOB_DONE   ;
+  if (!strcasecmp (name, "KILL"))     return PCONTROL_JOB_KILL   ;
+  if (!strcasecmp (name, "EXIT"))     return PCONTROL_JOB_EXIT   ;
+  if (!strcasecmp (name, "CRASH"))    return PCONTROL_JOB_CRASH  ;
+  return (PCONTROL_JOB_NONE);
 }
 
Index: trunk/Ohana/src/opihi/pcontrol/jobstack.c
===================================================================
--- trunk/Ohana/src/opihi/pcontrol/jobstack.c	(revision 36374)
+++ trunk/Ohana/src/opihi/pcontrol/jobstack.c	(revision 36375)
@@ -22,5 +22,5 @@
   /* print list */
   LockStack (stack);
-  gprint (GP_LOG, "Njobs: %d\n", stack[0].Nobject);
+  gprint (GP_LOG, "BEGIN BLOCK Njobs: %d\n", stack[0].Nobject);
   for (i = 0; i < stack[0].Nobject; i++) {
     job = stack[0].object[i];
Index: trunk/Ohana/src/opihi/pcontrol/status.c
===================================================================
--- trunk/Ohana/src/opihi/pcontrol/status.c	(revision 36374)
+++ trunk/Ohana/src/opihi/pcontrol/status.c	(revision 36375)
@@ -1,16 +1,93 @@
 # include "pcontrol.h"
 
-int PrintJobStack (int Nstack);
+int PrintJobStack (int Nstack, char *command, char *hostname, int state, float age);
 int PrintHostStack (int Nstack);
 
 int status (int argc, char **argv) {
 
-  PrintJobStack (PCONTROL_JOB_ALLJOBS);
-  PrintHostStack (PCONTROL_HOST_ALLHOSTS);
+  int N;
+
+  if (get_argument (argc, argv, "-h")) goto usage;
+  if (get_argument (argc, argv, "-help")) goto usage;
+  if (get_argument (argc, argv, "--help")) goto usage;
+
+  /* I would like to add the following options:
+   * strsub on argv[0]
+   * strsub on hostname, realhost
+   * list hostname and realhost
+   * filter by state
+   * filter by dtime
+  */
+
+  // -cmd (cmd)
+  // -host (hostname)
+  // -state (busy, pending, done, kill, exit, crash, resp, hung
+  // -age (seconds?) (minutes?)
+  // -nohost
+
+  char *COMMAND = NULL;
+  if ((N = get_argument (argc, argv, "-cmd"))) {
+    remove_argument (N, &argc, argv);
+    COMMAND = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  char *HOSTNAME = NULL;
+  if ((N = get_argument (argc, argv, "-host"))) {
+    remove_argument (N, &argc, argv);
+    HOSTNAME = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  int STATE = PCONTROL_JOB_ALLJOBS;
+  if ((N = get_argument (argc, argv, "-state"))) {
+    remove_argument (N, &argc, argv);
+    STATE = GetJobStackIDbyName (argv[N]);
+    remove_argument (N, &argc, argv);
+    if (STATE == PCONTROL_JOB_NONE) goto usage;
+  }
+
+  float AGE = 0.0;
+  if ((N = get_argument (argc, argv, "-age"))) {
+    remove_argument (N, &argc, argv);
+    AGE = atof (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  int SHOWHOST = TRUE;
+  if ((N = get_argument (argc, argv, "-nohost"))) {
+    remove_argument (N, &argc, argv);
+    SHOWHOST = FALSE;
+  }
+  if ((N = get_argument (argc, argv, "+jobs"))) {
+    remove_argument (N, &argc, argv);
+    SHOWHOST = FALSE;
+  }
+
+  int SHOWJOBS = TRUE;
+  if ((N = get_argument (argc, argv, "-nojobs"))) {
+    remove_argument (N, &argc, argv);
+    SHOWJOBS = FALSE;
+  }
+  if ((N = get_argument (argc, argv, "+host"))) {
+    remove_argument (N, &argc, argv);
+    SHOWJOBS = FALSE;
+  }
+
+  if (SHOWJOBS) {
+    PrintJobStack (PCONTROL_JOB_ALLJOBS, COMMAND, HOSTNAME, STATE, AGE);
+  }
+  if (SHOWHOST) {
+    PrintHostStack (PCONTROL_HOST_ALLHOSTS);
+  }
 
   return (TRUE);
+
+usage:
+  gprint (GP_ERR, "USAGE: status [-cmd command] [-host hostname] [-state state] [-age seconds] [+jobs,-nohost] [+host,-nojobs]\n");
+  return FALSE;
 }
 
-int PrintJobStack (int Nstack) {
+int PrintJobStack (int Nstack, char *command, char *hostname, int state, float age) {
 
   int i, j, Nobject;
@@ -30,10 +107,6 @@
     job = stack[0].object[i];
     ASSERT (job != NULL, "programming error");
-    if (job[0].realhost == NULL) {
-	gprint (GP_LOG, "%3d %9s ", i, job[0].hostname);
-    } else {
-	gprint (GP_LOG, "%3d %9s ", i, job[0].realhost);
-    }
-    gprint (GP_LOG, "%7s  ", GetJobStackName (job[0].state));
+
+    char *thishost = (job[0].realhost == NULL) ? job[0].hostname : job[0].realhost;
 
     switch (job[0].state) {
@@ -45,5 +118,4 @@
 	gettimeofday (&now, (void *) NULL);
 	dtime = DTIME (now, job[0].start);
-	gprint (GP_LOG, "%8.2f ", dtime);
 	break;
 
@@ -55,7 +127,29 @@
       default:
 	dtime = DTIME (job[0].stop, job[0].start);
-	gprint (GP_LOG, "%8.2f ", dtime);
 	break;
     }
+
+    // check on the filters
+    if (command) {
+      if (!strstr (job[0].argv[0], command)) continue;
+    }
+    if (hostname) {
+      if (!strstr (thishost, hostname)) continue;
+    }
+    if (age > 0.0) {
+      if (dtime < age) continue;
+    }
+    if (state != PCONTROL_JOB_ALLJOBS) {
+      // allow PCONTROL_JOB_RESP == BUSY
+      int validState = FALSE;
+      validState |= (state == PCONTROL_JOB_RESP) && (job[0].state == PCONTROL_JOB_BUSY);
+      validState |= (state == PCONTROL_JOB_BUSY) && (job[0].state == PCONTROL_JOB_RESP);
+      validState |= (state == job[0].state);
+      if (!validState) continue;
+    }
+
+    gprint (GP_LOG, "%3d %9s ", i, thishost);
+    gprint (GP_LOG, "%7s  ", GetJobStackName (job[0].state));
+    gprint (GP_LOG, "%8.2f ", dtime);
 
     PrintID (GP_LOG, job[0].JobID);
Index: trunk/ippconfig/gpc1/camera.config
===================================================================
--- trunk/ippconfig/gpc1/camera.config	(revision 36374)
+++ trunk/ippconfig/gpc1/camera.config	(revision 36375)
@@ -139,4 +139,5 @@
   CMF.XRAD STR {CHIP.NAME}.xrad # use .PSF and .EXT?
   CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+  CMF.XGAL STR {CHIP.NAME}.xgal # use .PSF and .EXT?
   CMF.DETEFF STR {CHIP.NAME}.deteff
 
Index: trunk/ippconfig/recipes/filerules-mef.mdc
===================================================================
--- trunk/ippconfig/recipes/filerules-mef.mdc	(revision 36374)
+++ trunk/ippconfig/recipes/filerules-mef.mdc	(revision 36375)
@@ -86,4 +86,5 @@
 PSPHOT.PSF.LOAD         INPUT    @FILES        CHIP       PSF       
 PSPHOT.INPUT.CMF        INPUT    @FILES        CHIP       CMF       
+PSPHOT.INPUT.CFF        INPUT    @FILES        CHIP       CFF
 PSPHOT.INPUT.TEXT       INPUT    @FILES        CHIP       SRCTEXT       
 PSPHOT.EXPNUM           INPUT    @FILES        CHIP       MASK       
@@ -247,4 +248,5 @@
 PSPHOT.OUT.CMF.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.cmf          CMF       NONE       CHIP       TRUE      SPLIT
 PSPHOT.OUT.CMF.MEF      OUTPUT {OUTPUT}.cmf                      CMF       NONE       CHIP       TRUE      MEF
+PSPHOT.OUTPUT.CFF       OUTPUT {OUTPUT}.cff                      CFF       NONE       CHIP       TRUE      MEF
 PSPHOT.PSF.RAW.SAVE     OUTPUT {OUTPUT}.psf                      PSF       NONE       CHIP       TRUE      MEF
 PSPHOT.PSF.SKY.SAVE     OUTPUT {OUTPUT}.psf                      PSF       NONE       CHIP       TRUE      MEF
Index: trunk/ippconfig/recipes/filerules-simple.mdc
===================================================================
--- trunk/ippconfig/recipes/filerules-simple.mdc	(revision 36374)
+++ trunk/ippconfig/recipes/filerules-simple.mdc	(revision 36375)
@@ -57,4 +57,5 @@
 PSPHOT.PSF.LOAD           INPUT    @FILES        CHIP       PSF       
 PSPHOT.INPUT.CMF          INPUT    @FILES        CHIP       CMF       
+PSPHOT.INPUT.CFF          INPUT    @FILES        CHIP       CFF
 PSPHOT.INPUT.TEXT         INPUT    @FILES        CHIP       SRCTEXT       
 PSPHOT.EXPNUM             INPUT    @FILES        CHIP       MASK
@@ -209,4 +210,5 @@
 PSPHOT.OUTPUT.CMF            OUTPUT {OUTPUT}.cmf                  CMF             NONE       CHIP       TRUE      NONE
 PSPHOT.OUT.CMF.MEF           OUTPUT {OUTPUT}.cmf                  CMF             NONE       CHIP       TRUE      NONE
+PSPHOT.OUTPUT.CFF            OUTPUT {OUTPUT}.cff                  CFF             NONE       CHIP       TRUE      NONE
 PSPHOT.PSF.RAW.SAVE          OUTPUT {OUTPUT}.psf                  PSF             NONE       FPA        TRUE      NONE
 PSPHOT.PSF.SKY.SAVE          OUTPUT {OUTPUT}.psf                  PSF             NONE       FPA        TRUE      NONE
Index: trunk/ippconfig/recipes/filerules-split.mdc
===================================================================
--- trunk/ippconfig/recipes/filerules-split.mdc	(revision 36374)
+++ trunk/ippconfig/recipes/filerules-split.mdc	(revision 36375)
@@ -77,4 +77,5 @@
 PSPHOT.PSF.LOAD           INPUT    @FILES        CHIP       PSF       
 PSPHOT.INPUT.CMF          INPUT    @FILES        CHIP       CMF       
+PSPHOT.INPUT.CFF          INPUT    @FILES        CHIP       CFF
 PSPHOT.INPUT.TEXT         INPUT    @FILES        CHIP       SRCTEXT       
 PSPHOT.EXPNUM             INPUT    @FILES        CHIP       EXPNUM
@@ -233,4 +234,5 @@
 PSPHOT.OUT.CMF.SPL           OUTPUT {OUTPUT}.{CHIP.NAME}.cmf          CMF             NONE       CHIP       TRUE      NONE
 PSPHOT.OUT.CMF.MEF           OUTPUT {OUTPUT}.cmf                      CMF             NONE       FPA        TRUE      MEF
+PSPHOT.OUTPUT.CFF            OUTPUT {OUTPUT}.cff                      CFF             NONE       CHIP       TRUE      NONE
 PSPHOT.PSF.RAW.SAVE          OUTPUT {OUTPUT}.{CHIP.NAME}.psf          PSF             NONE       CHIP       TRUE      MEF
 PSPHOT.PSF.SKY.SAVE          OUTPUT {OUTPUT}.psf                      PSF             NONE       FPA        TRUE      MEF
Index: trunk/ippconfig/recipes/ppSim.config
===================================================================
--- trunk/ippconfig/recipes/ppSim.config	(revision 36374)
+++ trunk/ippconfig/recipes/ppSim.config	(revision 36375)
@@ -61,4 +61,5 @@
 PSF.THETA	F32	0.0
 PSF.CONVOLVE    BOOL    FALSE		# generate fake stars with PSF (F), or smooth with PSF (T)?
+CONVOLVE.NSIGMA F32     5.0		# smooth image out to NSIGMA * sigma
 
 STARS.GRID      BOOL    FALSE		# Add grid of fake stars with constant flux
Index: trunk/ippconfig/recipes/psphot.config
===================================================================
--- trunk/ippconfig/recipes/psphot.config	(revision 36374)
+++ trunk/ippconfig/recipes/psphot.config	(revision 36375)
@@ -1,3 +1,2 @@
-
 # these options turn on different inputs and/or outputs
 SAVE.OUTPUT                         BOOL  TRUE
@@ -181,4 +180,5 @@
 EXT_FIT_MIN_TOL                     F32   0.01            # Fit tolerance for EXT
 EXT_FIT_MAX_TOL                     F32   2.00            # Fit tolerance for EXT
+EXT_FIT_NSIGMA_CONV                 F32   9.00            # Number of sigma for PSF convolved fits
 
 # the following is used to require a minimum quality of fit before
@@ -189,12 +189,13 @@
 
 # extended source aperture-like measurements
-EXTENDED_SOURCE_ANALYSIS            BOOL  FALSE  # perform any of the aperture-like measurements?
+EXTENDED_SOURCE_PETROSIAN           BOOL  FALSE
+EXTENDED_SOURCE_ANNULI              BOOL  FALSE
+EXTENDED_SOURCE_RAW_RADIUS          BOOL  FALSE  # use circular (T) or elliptical (F) contours for petrosians?
 EXTENDED_SOURCE_SN_LIM              F32   20.0
-EXTENDED_SOURCE_RAW_RADIUS          BOOL  FALSE
-EXTENDED_SOURCE_PETROSIAN           BOOL  FALSE
-EXTENDED_SOURCE_ISOPHOTAL           BOOL  FALSE
-EXTENDED_SOURCE_ANNULI              BOOL  FALSE
-EXTENDED_SOURCE_KRON                BOOL  FALSE
-PETROSIAN_FOR_STARS                 BOOL  FALSE
+
+EXT.NSIGMA.LIMIT.USE                BOOL  TRUE
+
+EXT.FIT.MIN.GAL.LIMIT               F32   10.0
+EXT.FIT.MIN.GAL.LIMIT.USE           BOOL  FALSE
 
 KRON_ITERATIONS                     S32   2
@@ -208,4 +209,5 @@
 EXTENDED_SOURCE_FITS                BOOL  FALSE  # perform the detailed extended sourc fits?
 EXTENDED_SOURCE_FITS_INTERACTIVE    BOOL  FALSE  # perform the detailed extended sourc fits?
+EXTENDED_SOURCE_FITS_POISSON        BOOL  FALSE   # use poisson errors for extended source models
 
 EXTENDED_SOURCE_MODELS_SELECTION    STR   ALL
@@ -258,10 +260,6 @@
 # Extended source fit parameters
 EXTENDED_TEST                       METADATA
-  EXTENDED_SOURCE_ANALYSIS            BOOL  TRUE  # perform any of the aperture-like measurements?
   EXTENDED_SOURCE_SN_LIM              F32   20.0
   EXTENDED_SOURCE_PETROSIAN           BOOL  TRUE
-  EXTENDED_SOURCE_ISOPHOTAL           BOOL  TRUE
-  EXTENDED_SOURCE_ANNULI              BOOL  TRUE
-  EXTENDED_SOURCE_KRON                BOOL  FALSE
 END
 
@@ -293,5 +291,5 @@
 INTERPOLATE_AP                      BOOL  TRUE
 
-POISSON.ERRORS.PHOT.LMM             BOOL  TRUE   
+POISSON.ERRORS.PHOT.LMM             BOOL  TRUE
 POISSON.ERRORS.PHOT.LIN             BOOL  FALSE
 POISSON.ERRORS.PARAMS               BOOL  TRUE
@@ -408,4 +406,15 @@
 END
 
+EXT.ANALYSIS.MAG.LIMITS METADATA
+    TYPE  DATA FILTER.ID MAG.LIMIT
+    gband DATA g         NAN            
+    rband DATA r         NAN       	 
+    iband DATA i         NAN		 
+    zband DATA z         NAN		 
+    yband DATA y         NAN		 
+    wband DATA w         NAN		 
+    other DATA any       NAN		 
+END
+
 # if true keep all matched sources, even if we get a non-finite aperture flux measurement
 PSPHOT.STACK.KEEP.BAD.MATCHES  BOOL     F
@@ -414,11 +423,21 @@
 RADIAL_APERTURES_SN_LIM             F32   0.0  # S/N limit for radial aperture calculation
 
+GALAXY_SHAPES                       BOOL  F
+GALAXY_SHAPES_FR_MAJOR_MIN          F32   0.5
+GALAXY_SHAPES_FR_MAJOR_MAX          F32   2.0
+GALAXY_SHAPES_FR_MAJOR_DEL          F32   0.1
+GALAXY_SHAPES_FR_MINOR_MIN          F32   0.5
+GALAXY_SHAPES_FR_MINOR_MAX          F32   2.0
+GALAXY_SHAPES_FR_MINOR_DEL          F32   0.1
+
+
 # Extended source fit parameters
 STACKPHOT                             METADATA
   EXTENDED_SOURCE_FITS                BOOL  TRUE  # perform any of the aperture-like measurements?
-  EXTENDED_SOURCE_ANALYSIS            BOOL  TRUE  # perform any of the aperture-like measurements?
   EXTENDED_SOURCE_SN_LIM              F32   10.0
   EXTENDED_SOURCE_PETROSIAN           BOOL  TRUE
   EXTENDED_SOURCE_ANNULI              BOOL  TRUE
+  EXT.NSIGMA.LIMIT.USE                BOOL  FALSE
+
   PSPHOT.STACK.MATCH.PSF.SOURCE       STR   AUTO # which inputs to convolve? (RAW, CNV, AUTO)
   PSPHOT.STACK.TARGET.PSF.AUTO        BOOL  F    # automatically determine target PSF size?
@@ -430,6 +449,5 @@
   RADIAL_APERTURES                    BOOL  T    # calculate flux in circular radial apertures?
   RADIAL_APERTURES_SN_LIM             F32   0.0  # S/N limit for radial aperture calculation
-  OUTPUT.FORMAT                       STR   PS1_SV1
-  PETROSIAN_FOR_STARS                 BOOL  FALSE
+  OUTPUT.FORMAT                       STR   PS1_SV2
 
   # subtract radial profiles for extremely bright stars?
@@ -441,4 +459,5 @@
   LMM_FIT_CHISQ_CONVERGENCE           BOOL  FALSE           # use the (old) chisq convergence or the new parameter one 
   LMM_FIT_GAIN_FACTOR_MODE            S32   2               # there are 3 options to determine the gain factor (0,1,2)
+  EXT_FIT_MIN_TOL                     F32   0.1             # Fit tolerance for EXT
 
   EXT_FIT_MAX_RADIUS                  F32   50.0
@@ -446,4 +465,6 @@
   EXT_MODEL                           STR   PS_MODEL_QGAUSS
   PEAKS_NMAX_TOTAL                    S32   0 # set this to limit the allowed number of peaks - Yields fault instead of avoid memory explosion
+
+  SAVE.RESID                          BOOL  TRUE
 END
 
@@ -451,8 +472,7 @@
 STACKPHOT_TEST                        METADATA
   EXTENDED_SOURCE_FITS                BOOL  F  # perform any of the aperture-like measurements?
-  EXTENDED_SOURCE_ANALYSIS            BOOL  F  # perform any of the aperture-like measurements?
   EXTENDED_SOURCE_SN_LIM              F32   10.0
   EXTENDED_SOURCE_PETROSIAN           BOOL  F
-  EXTENDED_SOURCE_ANNULI              BOOL  F
+
   PSPHOT.STACK.MATCH.PSF.SOURCE       STR   AUTO # which inputs to convolve? (RAW, CNV, AUTO)
   PSPHOT.STACK.TARGET.PSF.AUTO        BOOL  F    # automatically determine target PSF size?
@@ -471,8 +491,7 @@
 STACKPHOT_TEST_V1                     METADATA
   EXTENDED_SOURCE_FITS                BOOL  F  # perform any of the aperture-like measurements?
-  EXTENDED_SOURCE_ANALYSIS            BOOL  F  # perform any of the aperture-like measurements?
   EXTENDED_SOURCE_SN_LIM              F32   20.0
   EXTENDED_SOURCE_PETROSIAN           BOOL  TRUE
-  EXTENDED_SOURCE_ANNULI              BOOL  TRUE
+
   PSPHOT.STACK.MATCH.PSF.SOURCE       STR   AUTO # which inputs to convolve? (RAW, CNV, AUTO)
   PSPHOT.STACK.TARGET.PSF.AUTO        BOOL  F    # automatically determine target PSF size?
@@ -496,8 +515,7 @@
 SINGLE_TEST_V1                     METADATA
   EXTENDED_SOURCE_FITS                BOOL  F  # perform any of the aperture-like measurements?
-  EXTENDED_SOURCE_ANALYSIS            BOOL  F  # perform any of the aperture-like measurements?
   EXTENDED_SOURCE_SN_LIM              F32   20.0
   EXTENDED_SOURCE_PETROSIAN           BOOL  TRUE
-  EXTENDED_SOURCE_ANNULI              BOOL  TRUE
+
   PSPHOT.STACK.MATCH.PSF.SOURCE       STR   AUTO # which inputs to convolve? (RAW, CNV, AUTO)
   PSPHOT.STACK.TARGET.PSF.AUTO        BOOL  F    # automatically determine target PSF size?
@@ -506,9 +524,6 @@
   RADIAL_APERTURES                    BOOL  F    # calculate flux in circular radial apertures?
   RADIAL_APERTURES_SN_LIM             F32   0.0  # S/N limit for radial aperture calculation
-#  OUTPUT.FORMAT                       STR   PS1_SV1
   OUTPUT.FORMAT                       STR   PS1_V3
 
-  # CONSTANT_PHOTOMETRIC_WEIGHTS        BOOL  TRUE
-  # POISSON.ERRORS.PHOT.LMM             BOOL  FALSE
   CONSTANT_PHOTOMETRIC_WEIGHTS        BOOL  FALSE
   POISSON.ERRORS.PHOT.LMM             BOOL  TRUE
@@ -625,6 +640,18 @@
   # Extended source fit parameters
   # these models are used for high-quality shape analysis after all sources have been removed
+  SAVE.RESID BOOL T
+
   EXTENDED_SOURCE_FITS                BOOL  TRUE  # perform the detailed extended sourc fits?
-  EXT_MODEL                           STR   PS_MODEL_GAUSS
+  EXT_MODEL                           STR   PS_MODEL_QGAUSS
+  PSF_MODEL                           STR   PS_MODEL_PS1_V1
+
+  LMM_FIT_CHISQ_CONVERGENCE           BOOL  FALSE           # use the (old) chisq convergence or the new parameter one 
+  LMM_FIT_GAIN_FACTOR_MODE            S32   2               # there are 3 options to determine the gain factor (0,1,2)
+  EXT_FIT_MIN_TOL                     F32   0.1             # Fit tolerance for EXT
+
+  # XXX these are only needed if we want to get perfect fits of the synthetic data
+  PSF_FIT_RADIUS_SCALE         F32   3.75
+  EXT_FIT_NSIGMA_CONV          F32   11
+  PSF.RESIDUALS BOOL F
 
   # if this is defined, it gives a list of the model names to fit below (ALL or NONE are also valid)
@@ -651,2 +678,13 @@
   END
 END
+
+FULLFORCE_WARP METADATA
+    GALAXY_SHAPES           BOOL    TRUE
+    RADIAL_APERTURES        BOOL    TRUE
+    EXTENDED_SOURCE_FITS    BOOL    TRUE    # this casues the xfit extension to be written out
+    EXTENDED_SOURCE_PETROSIAN BOOL    TRUE  # I want petrosian mags
+    SAVE.PSF                BOOL    FALSE
+    SAVE.BACKMDL            BOOL    FALSE
+    # SAVE.RESID            BOOL    FALSE
+    # OUTPUT.FORMAT         STR     PS1_V3
+END
Index: trunk/ippconfig/recipes/reductionClasses.mdc
===================================================================
--- trunk/ippconfig/recipes/reductionClasses.mdc	(revision 36374)
+++ trunk/ippconfig/recipes/reductionClasses.mdc	(revision 36375)
@@ -172,4 +172,5 @@
         PSVIDEOPHOT             STR     PSVIDEOPHOT
         STATICSKY_CALIBRATION   STR     STATICSKY_CAL
+        FULLFORCE_PSPHOT  STR   FULLFORCE_WARP
 END
 
@@ -249,4 +250,5 @@
 	BACKGROUND_PPBACKGROUND	STR	BACKGROUND
 	BACKGROUND_PSWARP	STR	BACKGROUND
+        FULLFORCE_PSPHOT  STR   FULLFORCE_WARP
 END
 
Index: trunk/ippconfig/simple/camera.config
===================================================================
--- trunk/ippconfig/simple/camera.config	(revision 36374)
+++ trunk/ippconfig/simple/camera.config	(revision 36375)
@@ -61,4 +61,5 @@
   CMF.XFIT STR xfit
   CMF.DETEFF STR deteff
+  CMF.XGAL STR xgal
 
   PSF.HEAD  STR hdr
Index: trunk/ippconfig/simtest/format.config
===================================================================
--- trunk/ippconfig/simtest/format.config	(revision 36374)
+++ trunk/ippconfig/simtest/format.config	(revision 36375)
@@ -60,5 +60,5 @@
         FPA.LATITUDE    F64     20.7070999146 # North Latitude in Degrees 
         FPA.ELEVATION   F32     3048.0 # altitude in meters
-	CELL.SATURATION	F32	65535
+	CELL.SATURATION	F32	1000000
 	CELL.BAD	F32	0
 	CELL.READDIR	S32	1
Index: trunk/ppSim/src/ppSimInsertStars.c
===================================================================
--- trunk/ppSim/src/ppSimInsertStars.c	(revision 36374)
+++ trunk/ppSim/src/ppSimInsertStars.c	(revision 36375)
@@ -78,4 +78,5 @@
 
     FILE *outfile = fopen (outname, "w");
+    psAssert (outfile, "cannot write output");
 
     float radius = -1.0;
Index: trunk/ppSim/src/ppSimSmoothReadout.c
===================================================================
--- trunk/ppSim/src/ppSimSmoothReadout.c	(revision 36374)
+++ trunk/ppSim/src/ppSimSmoothReadout.c	(revision 36375)
@@ -4,20 +4,36 @@
 {
     bool status;
+    psTimerStart ("ppSmooth");
 
-    // XXX use these defaults?
-    // float minGauss = 0.1;
-    float nSigma = 4.0;
+    float nSigma = psMetadataLookupF32(&status, recipe, "CONVOLVE.NSIGMA"); // SIGMA convolutions (pixels)
+    if (!status) nSigma = 5.0;
+
     float sigma = psMetadataLookupF32(&status, recipe, "SEEING"); // Seeing SIGMA (pixels)
 
-    // bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading in psImageConvolve
+    char *modelName = psMetadataLookupStr(&status, recipe, "PSF.MODEL"); // Seeing SIGMA (pixels)
+    if (!strcmp (modelName, "PS_MODEL_GAUSS")) {
+      // bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading in psImageConvolve
+      // smooth the image in place, applying the mask as we go
+      psImageSmooth(input->image, sigma, nSigma);
+      psLogMsg("ppSmooth", PS_LOG_MINUTIA, "smooth image: %f sec\n", psTimerMark("ppSmooth"));
+      return true;
+    }
 
-    psTimerStart ("ppSmooth");
+    if (!strcmp (modelName, "PS_MODEL_PS1_V1")) {
+      // bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading in psImageConvolve
+      // smooth the image in place, applying the mask as we go
+      psImageSmooth2dCacheData *smdata = psImageSmooth2dCacheAlloc(nSigma);
+      psImageSmooth2dCacheKernel_PS1_V1 (smdata, sigma, 1.0);
 
-    // smooth the image in place, applying the mask as we go
-    psImageSmooth(input->image, sigma, nSigma);
-    psLogMsg("ppSmooth", PS_LOG_MINUTIA, "smooth image: %f sec\n", psTimerMark("ppSmooth"));
+      psImageSmooth2dCache_F32 (input->image, smdata);
+
+      psFree (smdata);
+      psLogMsg("ppSmooth", PS_LOG_MINUTIA, "smooth image: %f sec\n", psTimerMark("ppSmooth"));
+      return true;
+    }
 
     // psImageConvolveSetThreads(oldThreads);
-    return true;
+    psLogMsg("ppSmooth", PS_LOG_MINUTIA, "failed to smooth image: %f sec\n", psTimerMark("ppSmooth"));
+    return false;
 }
 
Index: trunk/psLib/src/imageops/Makefile.am
===================================================================
--- trunk/psLib/src/imageops/Makefile.am	(revision 36374)
+++ trunk/psLib/src/imageops/Makefile.am	(revision 36375)
@@ -7,4 +7,5 @@
 	psImageBackground.c \
 	psImageConvolve.c \
+	psImageConvolve2dCache.c \
 	psImageCovariance.c \
 	psImageGeomManip.c \
Index: trunk/psLib/src/imageops/psImageConvolve.c
===================================================================
--- trunk/psLib/src/imageops/psImageConvolve.c	(revision 36374)
+++ trunk/psLib/src/imageops/psImageConvolve.c	(revision 36375)
@@ -36,9 +36,6 @@
 #define MIN_GAUSS_FRAC 0.25             // Minimum Gaussian fraction to accept when smoothing
 
-
-
 static bool threaded = false;           // Run image convolution threaded?
 static pthread_mutex_t threadMutex = PTHREAD_MUTEX_INITIALIZER;
-
 
 static void kernelFree(psKernel *kernel)
@@ -780,5 +777,5 @@
 }
 
-void psImageSmooth_PreAlloc_DataFree (psImageSmooth_PreAlloc_Data *smdata) {
+void psImageSmoothCacheDataFree (psImageSmoothCacheData *smdata) {
     psFree (smdata->resultX);
     psFree (smdata->resultY);
@@ -786,8 +783,11 @@
 }
 
-psImageSmooth_PreAlloc_Data *psImageSmooth_PreAlloc_DataAlloc (psImage *image, double sigma, double Nsigma) {
-
-    psImageSmooth_PreAlloc_Data *smdata = psAlloc(sizeof(psImageSmooth_PreAlloc_Data));
-    psMemSetDeallocator(smdata, (psFreeFunc) psImageSmooth_PreAlloc_DataFree);
+// allocate the psImageSmoothCache data structure, but do not define the kernel
+psImageSmoothCacheData *psImageSmoothCacheAlloc (psImage *image, double sigma, double Nsigma) {
+
+    psImageSmoothCacheData *smdata = psAlloc(sizeof(psImageSmoothCacheData));
+    psMemSetDeallocator(smdata, (psFreeFunc) psImageSmoothCacheDataFree);
+
+    smdata->kernel = NULL;
 
     if (!image) {
@@ -796,5 +796,4 @@
 	smdata->Nx = 0;
 	smdata->Ny = 0;
-	smdata->kernel = NULL;
 	smdata->resultX = NULL;
 	smdata->resultY = NULL;
@@ -807,5 +806,6 @@
     smdata->Ny = image->numRows;            // Number of rows
 
-    IMAGE_SMOOTH_GAUSS(smdata->kernel, smdata->Nrange, sigma, F32);
+    // XXX drop this : we now require a call to a kernel-creation function (like psImageSmoothCacheKernel_Gauss)
+    // IMAGE_SMOOTH_GAUSS(smdata->kernel, smdata->Nrange, sigma, F32);
        
     // use a temp running buffer for X and Y directions.
@@ -819,8 +819,18 @@
 }
 
+// generate a Gaussian smoothing kernel for supplied sigma.  sigma here does not need to match
+// that used to allocate the structure, but it is recommended
+bool psImageSmoothCacheKernel_Gauss (psImageSmoothCacheData *smdata, float sigma) {
+    // check for NULL structure elements?
+    psFree (smdata->kernel);
+    IMAGE_SMOOTH_GAUSS(smdata->kernel, smdata->Nrange, sigma, F32);
+    return true;
+}
+
 // we can use the same DATA structure on multiple images of the same size
-bool psImageSmooth_PreAlloc_F32(psImage *image, psImageSmooth_PreAlloc_Data *smdata)
+bool psImageSmoothCache_F32(psImage *image, psImageSmoothCacheData *smdata)
 {
     PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_VECTOR_NON_NULL(smdata->kernel, false);
     // assert on data type
 
@@ -2156,3 +2166,2 @@
     return threaded;
 }
-
Index: trunk/psLib/src/imageops/psImageConvolve.h
===================================================================
--- trunk/psLib/src/imageops/psImageConvolve.h	(revision 36374)
+++ trunk/psLib/src/imageops/psImageConvolve.h	(revision 36375)
@@ -26,5 +26,5 @@
 #define PS_TYPE_KERNEL_NAME "psF32"    ///< the data type for kernel as a string */
 
-/// a structure to contain data related to image smoothing with a 1D gauss kernel
+/// a structure to contain data related to image smoothing with a pre-cached 1D gauss kernel
 typedef struct {
     int Nx;
@@ -34,5 +34,12 @@
     psF32 *resultY;
     psVector *kernel;
-} psImageSmooth_PreAlloc_Data;
+} psImageSmoothCacheData;
+
+/// a structure to contain data related to image smoothing with a pre-cached 1D gauss kernel
+typedef struct {
+    float Nsigma;
+    int Ns;				// number of pixel radii
+    float *radflux;			// conv kernel in special positions
+} psImageSmooth2dCacheData;
 
 /// A convolution kernel
@@ -306,6 +313,7 @@
 );
 
-psImageSmooth_PreAlloc_Data *psImageSmooth_PreAlloc_DataAlloc (psImage *image, double sigma, double Nsigma);
-bool psImageSmooth_PreAlloc_F32(psImage *image, psImageSmooth_PreAlloc_Data *smdata);
+psImageSmoothCacheData *psImageSmoothCacheAlloc (psImage *image, double sigma, double Nsigma);
+bool psImageSmoothCache_F32(psImage *image, psImageSmoothCacheData *smdata);
+bool psImageSmoothCacheKernel_Gauss (psImageSmoothCacheData *smdata, float sigma);
 
 /// Control threading for image convolution functions
@@ -318,4 +326,9 @@
 bool psImageConvolveGetThreads(void);
 
+psImageSmooth2dCacheData *psImageSmooth2dCacheAlloc (float Nsigma);
+bool psImageSmooth2dCacheKernel_PS1_V1 (psImageSmooth2dCacheData *smdata, float sigma, float kappa);
+bool psImageSmooth2dCacheKernel_Gauss (psImageSmooth2dCacheData *smdata, float sigma);
+bool psImageSmooth2dCache_F32(psImage *image, psImageSmooth2dCacheData *smdata);
+
 /// @}
 #endif // #ifndef PS_IMAGE_CONVOLVE_H
Index: trunk/psLib/src/imageops/psImageConvolve2dCache.c
===================================================================
--- trunk/psLib/src/imageops/psImageConvolve2dCache.c	(revision 36375)
+++ trunk/psLib/src/imageops/psImageConvolve2dCache.c	(revision 36375)
@@ -0,0 +1,405 @@
+/// @file  psImageConvolve2dCache.c -- specialized 2D convolution for psf matching
+/// @author Eugene Magnier, IfA
+///
+/// @date $Date: 2009-02-05 23:56:14 $
+/// Copyright 2004-2007 Institute for Astronomy, University of Hawaii
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+#include "psAbort.h"
+#include "psMemory.h"
+#include "psLogMsg.h"
+#include "psError.h"
+#include "psAssert.h"
+#include "psScalar.h"
+#include "psBinaryOp.h"
+#include "psImageFFT.h"
+#include "psImageStructManip.h"
+#include "psImagePixelManip.h"
+#include "psTrace.h"
+#include "psThread.h"
+
+#include "psImageConvolve.h"
+
+// fixed set of radii and number of entries in circularly symmetric profile
+# define NRAD_MAX 87
+# define  RAD_MAX 15
+static float radii2[NRAD_MAX] = {   0.0,   1.0,   2.0,   4.0,   5.0, 
+				    8.0,   9.0,  10.0,  13.0,  16.0, 
+				    17.0,  18.0,  20.0,  25.0,  26.0, 
+
+				    29.0,  32.0,  34.0,  36.0,  37.0, 
+				    40.0,  41.0,  45.0,  49.0,  50.0, 
+				    52.0,  53.0,  58.0,  61.0,  64.0,
+
+				    65.0,  68.0,  72.0,  73.0,  74.0, 
+				    80.0,  81.0,  82.0,  85.0,  89.0,
+				    90.0,  97.0,  98.0, 100.0, 101.0, 
+
+				    104.0, 106.0, 109.0, 113.0, 116.0, 
+				    117.0, 121.0, 122.0, 125.0, 128.0, 
+				    130.0, 136.0, 137.0, 144.0, 145.0, 
+
+				    146.0, 148.0, 149.0, 153.0, 157.0, 
+				    160.0, 162.0, 164.0, 169.0, 170.0, 
+				    173.0, 178.0, 180.0, 181.0, 185.0, 
+
+				    193.0, 194.0, 196.0, 197.0, 200.0, 
+				    202.0, 205.0, 208.0, 212.0, 218.0, 
+				    221.0, 225.0
+};
+
+static int   radiiN[NRAD_MAX] = {    1,  4, 4,  4,  8,
+				     4,  4, 8,  8,  4,
+				     8,  4, 8, 12,  8, 
+
+				     8,  4, 8,  4,  8,
+				     8,  8, 8,  4, 12,
+				     8,  8, 8,  8,  4, 
+
+				     16, 8, 4,  8,  8, 
+				     8, 4, 8, 16,  8, 
+				     8, 8, 4, 12, 8,
+
+				     8, 8, 8, 8, 8, 
+				     8, 4, 8, 16, 4, 
+				     16, 8, 8, 8, 16, 
+
+				     8, 8, 8, 8, 8, 
+				     8, 4, 8, 12, 16,
+				     8, 8, 8, 8, 16,
+
+				     8, 8, 4, 8, 12,
+				     8, 16, 8, 8, 8, 
+				     16, 12
+};
+
+# define ADD_AXIS(RAD,DD) s += radflux[RAD]*(vi[iy - DD][ix] + \
+					     vi[iy + DD][ix] + \
+					     vi[iy][ix - DD] + \
+					     vi[iy][ix + DD]); 
+
+# define ADD_DIAG(RAD,DD) s += radflux[RAD]*(vi[iy - DD][ix - DD] + \
+					     vi[iy - DD][ix + DD] + \
+					     vi[iy + DD][ix - DD] + \
+					     vi[iy + DD][ix + DD]);
+
+# define ADD_RAND(RAD,DX,DY) s += radflux[RAD]*(vi[iy - DY][ix - DX] + \
+						vi[iy - DY][ix + DX] + \
+						vi[iy + DY][ix - DX] + \
+						vi[iy + DY][ix + DX] + \
+						vi[iy - DX][ix - DY] + \
+						vi[iy - DX][ix + DY] + \
+						vi[iy + DX][ix - DY] + \
+						vi[iy + DX][ix + DY]);
+
+# if (0)
+# define ADD_AXIS(RAD,DD) s += radflux[RAD]*(vi[p - DD] + vi[p + DD] + vi[p - DD*Nx] + vi[p + DD*Nx]);
+# define ADD_DIAG(RAD,DD) s += radflux[RAD]*(vi[p - DD - DD*Nx] + vi[p + DD - DD*Nx] + vi[p - DD + DD*Nx] + vi[p + DD + DD*Nx]);
+# define ADD_RAND(RAD,DX,DY) s += radflux[RAD]*(vi[p - DX - DY*Nx] + vi[p + DX - DY*Nx] + vi[p - DX + DY*Nx] + vi[p + DX + DY*Nx] + 
+ 						vi[p - DY - DX*Nx] + vi[p + DY - DX*Nx] + vi[p - DY + DX*Nx] + vi[p + DY + DX*Nx]);
+# endif
+
+void psImageSmooth2dCacheDataFree (psImageSmooth2dCacheData *smdata) {
+
+    if (smdata->radflux == NULL) return;
+    psFree (smdata->radflux);
+}
+
+// allocate the psImageSmooth2dCache data structure, but do not define the kernel
+psImageSmooth2dCacheData *psImageSmooth2dCacheAlloc (float Nsigma) {
+
+    psAssert (isfinite(Nsigma) && (Nsigma > 0.5), "Nsigma is not valid");
+
+    psImageSmooth2dCacheData *smdata = psAlloc(sizeof(psImageSmooth2dCacheData));
+    psMemSetDeallocator(smdata, (psFreeFunc) psImageSmooth2dCacheDataFree);
+
+    smdata->radflux = NULL;
+    smdata->Nsigma = Nsigma;
+    smdata->Ns = -1;
+
+    return smdata;
+}
+
+// generate a 2D smoothing kernel for supplied sigma & kappa (PS1_V1 profile).  
+bool psImageSmooth2dCacheKernel_PS1_V1 (psImageSmooth2dCacheData *smdata, float sigma, float kappa) {
+
+    // check for NULL structure elements?
+    int Ns = (int)(smdata->Nsigma * sigma);
+    Ns = PS_MAX (3, PS_MIN (Ns, RAD_MAX));
+    smdata->Ns = Ns;
+
+    int Ns2 = Ns * Ns;
+
+    // we are going to use a hard-wired set of radial points
+    smdata->radflux = psAlloc(sizeof(float)*NRAD_MAX);
+
+    double sum = 0.0;
+    for (int i = 0; i < NRAD_MAX; i++) {
+	if (radii2[i] > Ns2) {
+	    smdata->radflux[i] = 0.0;
+	    continue;
+	}
+	float z = 0.5 * radii2[i] / PS_SQR(sigma);
+	smdata->radflux[i] = 1.0 / (1.0 + kappa*z + pow(z,1.666));
+	sum += radiiN[i] * smdata->radflux[i];
+    }
+    for (int i = 0; i < NRAD_MAX; i++) {
+	smdata->radflux[i] = smdata->radflux[i] / sum;
+    }
+
+    return true;
+}
+
+// generate a 2D smoothing kernel for supplied sigma & kappa (PS1_V1 profile).  
+bool psImageSmooth2dCacheKernel_Gauss (psImageSmooth2dCacheData *smdata, float sigma) {
+
+    // check for NULL structure elements?
+    int Ns = (int)(smdata->Nsigma * sigma);
+    Ns = PS_MAX (3, PS_MIN (Ns, RAD_MAX));
+    smdata->Ns = Ns;
+
+    int Ns2 = Ns * Ns;
+
+    // we are going to use a hard-wired set of radial points
+    smdata->radflux = psAlloc(sizeof(float)*NRAD_MAX);
+
+    float sum = 0.0;
+    for (int i = 0; i < NRAD_MAX; i++) {
+	if (radii2[i] > Ns2) {
+	    smdata->radflux[i] = 0.0;
+	    continue;
+	}
+	float z = 0.5 * radii2[i] / PS_SQR(sigma);
+	smdata->radflux[i] = exp(-z);
+	sum += radiiN[i] * smdata->radflux[i];
+    }
+    for (int i = 0; i < NRAD_MAX; i++) {
+	smdata->radflux[i] = smdata->radflux[i] / sum;
+    }
+
+    return true;
+}
+
+// we can use the same DATA structure on multiple images of the same size
+bool psImageSmooth2dCache_F32(psImage *image, psImageSmooth2dCacheData *smdata)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(smdata->radflux, false);
+    // assert on data type
+
+    // relevant terms
+    int Ns = smdata->Ns;    // Number of pixels either side for convolution kernel
+       
+    int Nx = image->numCols;
+    int Ny = image->numRows;
+
+    int Nxtmp = Nx + 2*Ns;
+    int Nytmp = Ny + 2*Ns;
+
+    // copy input image into a buffer padded by Ns on either side
+    float **vi = (float **) psAlloc(sizeof(float *)*Nytmp);
+    for (int iy = 0; iy < Nytmp; iy ++) {
+	int Iy = iy - Ns;
+	vi[iy] = (float *) psAlloc(sizeof(float)*Nxtmp);
+	memset (vi[iy], 0, sizeof(float)*Nxtmp);
+	if (Iy < 0) continue;
+	if (Iy >= image->numRows) continue;
+	for (int Ix = 0; Ix < image->numCols; Ix ++) {
+	    int ix = Ix + Ns;
+	    vi[iy][ix] = image->data.F32[Iy][Ix];
+	}
+    }
+
+    // set up the output buffer (different from input)
+    float **vo = (float **) psAlloc(sizeof(float *)*Nytmp);
+    for (int iy = 0; iy < Nytmp; iy ++) {
+	vo[iy] = (float *) psAlloc(sizeof(float)*Nxtmp);
+	memset (vo[iy], 0, sizeof(float)*Nxtmp);
+    }
+
+    float *radflux = smdata->radflux;
+
+    // smooth in 2D (ix,iy is the coordinate in the input and output images, only need to
+    // transform the region inside the padding.
+    for (int iy = Ns; iy < Ny + Ns; iy ++) {
+	for (int ix = Ns; ix < Nx + Ns; ix ++) {
+
+	    float s = radflux[0] * vi[iy][ix];
+
+	    // r <= 1.0
+	    ADD_AXIS (1,  1);      // r^2 = 1
+
+	    // r <= 2.0
+	    ADD_DIAG (2,  1);      // r^2 = 2
+	    ADD_AXIS (3,  2);      // r^2 = 4
+
+	    // r <= 3.0
+	    ADD_RAND (4,  1,  2);  // r^2 = 5
+	    ADD_DIAG (5,  2);      // r^2 = 8
+	    ADD_AXIS (6,  3);      // r^2 = 9
+	    if (Ns <= 3) goto finish;
+
+	    // r <= 4.0
+	    ADD_RAND (7,  1,  3);  // r^2 = 10
+	    ADD_RAND (8,  2,  3);  // r^2 = 13
+	    ADD_AXIS (9,  4);      // r^2 = 16
+	    if (Ns <= 4) goto finish;
+
+	    // r <= 5.0
+	    ADD_RAND (10,  1,  4); // r^2 = 17
+	    ADD_DIAG (11,  3);     // r^2 = 18
+	    ADD_RAND (12,  2,  4); // r^2 = 20
+	    ADD_RAND (13,  3,  4); // r^2 = 25
+	    ADD_AXIS (13,  5);     // r^2 = 25
+	    if (Ns <= 5) goto finish;
+
+	    // r <= 6.0
+	    ADD_RAND (14,  1,  5); // r^2 = 26
+	    ADD_RAND (15,  2,  5); // r^2 = 29
+	    ADD_DIAG (16,  4);     // r^2 = 32
+	    ADD_RAND (17,  3,  5); // r^2 = 34
+	    ADD_AXIS (18,  6);     // r^2 = 36
+	    if (Ns <= 6) goto finish;
+
+	    // r <= 7.0
+	    ADD_RAND (19,  1,  6); // r^2 = 37
+	    ADD_RAND (20,  2,  6); // r^2 = 40
+	    ADD_RAND (21,  4,  5); // r^2 = 41
+	    ADD_RAND (22,  3,  6); // r^2 = 45
+	    ADD_AXIS (23,  7);     // r^2 = 49
+	    if (Ns <= 7) goto finish;
+
+	    // r <= 8.0
+	    ADD_RAND (24,  1,  7); // r^2 = 50
+	    ADD_DIAG (24,  5);     // r^2 = 50
+	    ADD_RAND (25,  4,  6); // r^2 =   52
+	    ADD_RAND (26,  2,  7); // r^2 =   53
+	    ADD_RAND (27,  3,  7); // r^2 =   58
+	    ADD_RAND (28,  5,  6); // r^2 =   61
+	    ADD_AXIS (29,  8);     // r^2 =   64
+	    if (Ns <= 8) goto finish;
+
+	    ADD_RAND (30,  1,  8); // r^2 =   65 *
+	    ADD_RAND (30,  4,  7); // r^2 =   65 *
+	    ADD_RAND (31,  2,  8); // r^2 =   68
+	    ADD_DIAG (32,  6);     // r^2 =   72
+	    ADD_RAND (33,  3,  8); // r^2 =   73
+	    ADD_RAND (34,  5,  7); // r^2 =   74
+	    ADD_RAND (35,  4,  8); // r^2 =   80
+	    ADD_AXIS (36,  9);     // r^2 =   81
+	    if (Ns <= 9) goto finish;
+
+	    ADD_RAND (37,  1,  9); // r^2 =   82
+	    ADD_RAND (38,  2,  9); // r^2 =   85 *
+	    ADD_RAND (38,  6,  7); // r^2 =   85 *
+	    ADD_RAND (39,  5,  8); // r^2 =   89
+	    ADD_RAND (40,  3,  9); // r^2 =   90
+	    ADD_RAND (41,  4,  9); // r^2 =   97
+	    ADD_DIAG (42,  7);     // r^2 =   98
+	    ADD_RAND (43,  6,  8); // r^2 =  100 *
+	    ADD_AXIS (43, 10);     // r^2 =  100 *
+	    if (Ns <= 10) goto finish;
+
+	    ADD_RAND ( 44,  1, 10);  // r^2 = 101
+	    ADD_RAND ( 45,  2, 10);  // r^2 = 104
+	    ADD_RAND ( 46,  5,  9);  // r^2 = 106
+	    ADD_RAND ( 47,  3, 10);  // r^2 = 109
+	    ADD_RAND ( 48,  7,  8);  // r^2 = 113
+	    ADD_RAND ( 49,  4, 10);  // r^2 = 116
+	    ADD_RAND ( 50,  6,  9);  // r^2 = 117
+	    ADD_AXIS ( 51, 11)    ;  // r^2 = 121
+	    if (Ns <= 11) goto finish;
+
+	    ADD_RAND ( 52,  1, 11);  // r^2 = 122
+	    ADD_RAND ( 53,  2, 11);  // r^2 = 125 *
+	    ADD_RAND ( 53,  5, 10);  // r^2 = 125 *
+	    ADD_DIAG ( 54,  8)    ;  // r^2 = 128
+	    ADD_RAND ( 55,  3, 11);  // r^2 = 130 *
+	    ADD_RAND ( 55,  7,  9);  // r^2 = 130 *
+	    ADD_RAND ( 56,  6, 10);  // r^2 = 136
+	    ADD_RAND ( 57,  4, 11);  // r^2 = 137
+	    ADD_AXIS ( 58, 12)    ;  // r^2 = 144
+	    if (Ns <= 12) goto finish;
+
+	    ADD_RAND ( 59,  1, 12);  // r^2 = 145 *
+	    ADD_RAND ( 59,  8,  9);  // r^2 = 145 *
+	    ADD_RAND ( 60,  5, 11);  // r^2 = 146
+	    ADD_RAND ( 61,  2, 12);  // r^2 = 148
+	    ADD_RAND ( 62,  7, 10);  // r^2 = 149
+	    ADD_RAND ( 63,  3, 12);  // r^2 = 153
+	    ADD_RAND ( 64,  6, 11);  // r^2 = 157
+	    ADD_RAND ( 65,  4, 12);  // r^2 = 160
+	    ADD_DIAG ( 66,  9)    ;  // r^2 = 162
+	    ADD_RAND ( 67,  8, 10);  // r^2 = 164
+	    ADD_RAND ( 68,  5, 12);  // r^2 = 169 *
+	    ADD_AXIS ( 68, 13)    ;  // r^2 = 169 *
+	    if (Ns <= 13) goto finish;
+
+	    ADD_RAND ( 69,  1, 13);  // r^2 = 170 *
+	    ADD_RAND ( 69,  7, 11);  // r^2 = 170 *
+	    ADD_RAND ( 70,  2, 13);  // r^2 = 173
+	    ADD_RAND ( 71,  3, 13);  // r^2 = 178
+	    ADD_RAND ( 72,  6, 12);  // r^2 = 180
+	    ADD_RAND ( 73,  9, 10);  // r^2 = 181
+	    ADD_RAND ( 74,  4, 13);  // r^2 = 185 *
+	    ADD_RAND ( 74,  8, 11);  // r^2 = 185 *
+	    ADD_RAND ( 75,  7, 12);  // r^2 = 193
+	    ADD_RAND ( 76,  5, 13);  // r^2 = 194
+	    ADD_AXIS ( 77, 14)    ;  // r^2 = 196
+	    if (Ns <= 14) goto finish;
+
+	    ADD_RAND ( 78,  1, 14);  // r^2 = 197
+	    ADD_DIAG ( 79, 10)    ;  // r^2 = 200 *
+	    ADD_RAND ( 79,  2, 14);  // r^2 = 200 *
+	    ADD_RAND ( 80,  9, 11);  // r^2 = 202
+	    ADD_RAND ( 81,  3, 14);  // r^2 = 205 *
+	    ADD_RAND ( 81,  6, 13);  // r^2 = 205 *
+	    ADD_RAND ( 82,  8, 12);  // r^2 = 208
+	    ADD_RAND ( 83,  4, 14);  // r^2 = 212
+	    ADD_RAND ( 84,  7, 13);  // r^2 = 218
+	    ADD_RAND ( 85, 10, 11);  // r^2 = 221 *
+	    ADD_RAND ( 85,  5, 14);  // r^2 = 221 *
+	    ADD_RAND ( 86,  9, 12);  // r^2 = 225 *
+	    ADD_AXIS ( 86, 15);      // r^2 = 225 *
+	    if (Ns <= 15) goto finish;
+
+	finish:
+	    vo[iy][ix] = s;
+	}
+    }
+    
+    for (int iy = 0; iy < Ny; iy ++) {
+	int Iy = iy + Ns;
+	for (int ix = 0; ix < Nx; ix ++) {
+	    int Ix = ix + Ns;
+	    image->data.F32[iy][ix] = vo[Iy][Ix];
+	}
+    }
+
+    for (int iy = 0; iy < Nytmp; iy ++) {
+	psFree (vi[iy]);
+	psFree (vo[iy]);
+    }
+    psFree (vi);
+    psFree (vo);
+	
+    return true;
+}
+
+/* 
+101, 104, 106, 109, 113, 
+116, 117, 121, 122, 125, 
+128, 130, 136, 137, 144, 
+145, 146, 148, 149, 153, 
+157, 160, 162, 164, 169, 
+170, 173, 178, 180, 181, 
+185, 193, 194, 196, 197, 
+200, 202, 205, 208, 212, 
+218, 221, 225
+*/
+
Index: trunk/psLib/src/math/psEllipse.c
===================================================================
--- trunk/psLib/src/math/psEllipse.c	(revision 36374)
+++ trunk/psLib/src/math/psEllipse.c	(revision 36375)
@@ -12,4 +12,7 @@
 // f = exp(-z) where z describes the elliptical contour at any flux level:
 
+// NOTE: major, minor are the 1-sigma lengths in the major,minor directions assuming the
+// moments represent a Gaussian profile
+
 // sigma shape: z = 0.5((x/sx)^2 + (y/sy)^2 + sxy*x*y)
 // sigma axes : z = 0.5((x/sa)^2 + (y/sb)^2), x,y rotated by theta 
@@ -17,5 +20,123 @@
 // polarization : e0, e1, e2
 
-// ellipse rotation (major, minor, theta) -> (x2, y2, xy)
+// ellipse rotation (major, minor, theta) -> (Mxx, Mxy, Myy)
+psEllipseMoments psEllipseAxesToMoments(psEllipseAxes axes)
+{
+    psEllipseMoments moments;
+
+    double f1 = PS_SQR(axes.major) + PS_SQR(axes.minor);
+    double f2 = PS_SQR(axes.major) - PS_SQR(axes.minor);
+
+    moments.x2 = +0.5*f1 + 0.5*f2*cos(2*axes.theta);
+    moments.y2 = +0.5*f1 - 0.5*f2*cos(2*axes.theta);
+    moments.xy = +0.5*f2*sin(2*axes.theta);
+
+    assert (isfinite(moments.x2));
+    assert (isfinite(moments.y2));
+    assert (isfinite(moments.xy));
+
+    return moments;
+}
+
+// ellipse rotation (Mxx, Mxy, Myy) -> (major, minor, theta).
+psEllipseAxes psEllipseMomentsToAxes(psEllipseMoments moments, double maxAR)
+{
+    psEllipseAxes axes;
+    psEllipseAxes badValue = {NAN, NAN, NAN};
+
+    if (!isfinite(moments.x2)) return badValue;
+    if (!isfinite(moments.y2)) return badValue;
+    if (!isfinite(moments.xy)) return badValue;
+
+    if (moments.x2 < 0) return badValue;
+    if (moments.y2 < 0) return badValue;
+
+    double g1 = moments.x2 + moments.y2;
+    double g2 = moments.x2 - moments.y2;
+    double g3 = sqrt(PS_SQR(g2) + 4*PS_SQR(moments.xy));
+
+    axes.major = sqrt (0.5*(g1 + g3));
+    axes.theta = +0.5 * atan2 (+2.0*moments.xy, g2); // theta in radians
+
+    // long, thin objects are likely to have a poorly measured minor axis
+    // the angle and major axis are likely to be ok.
+    // restrict the axis ratio
+    double rAR2 = (g1 - g3) / (g1 + g3);
+    if (rAR2 < 1.0/PS_SQR(maxAR)) {
+        axes.minor = axes.major / maxAR;
+    } else {
+        axes.minor = sqrt (0.5*(g1 - g3));
+    }
+
+    assert (isfinite(axes.major));
+    assert (isfinite(axes.minor));
+    assert (isfinite(axes.theta));
+
+    return axes;
+}
+
+// ellipse rotation (major, minor, theta) -> (sx, sy, sxy)
+// theta is postive rotation of major axis away from x-axis
+psEllipseShape psEllipseAxesToShape(psEllipseAxes axes)
+{
+    psEllipseShape shape;
+    psEllipseShape badValue = {NAN, NAN, NAN};
+
+    if (!isfinite(axes.minor)) return badValue;
+    if (!isfinite(axes.major)) return badValue;
+    if (!isfinite(axes.theta)) return badValue;
+
+    if (axes.minor <= 0) return badValue;
+    if (axes.major <= 0) return badValue;
+
+    double f1 = 1.0 / PS_SQR(axes.minor) + 1.0 / PS_SQR(axes.major);
+    double f2 = 1.0 / PS_SQR(axes.minor) - 1.0 / PS_SQR(axes.major);
+
+    double sxr = 0.5*f1 - 0.5*f2*cos(2*axes.theta);
+    double syr = 0.5*f1 + 0.5*f2*cos(2*axes.theta);
+
+    // sxr, syr cannot be < 0 (f1 >= f2)
+
+    shape.sx  = +1.0 / sqrt(sxr);
+    shape.sy  = +1.0 / sqrt(syr);
+    shape.sxy = -0.5*f2*sin(2*axes.theta);
+
+    assert (isfinite(shape.sx));
+    assert (isfinite(shape.sy));
+    assert (isfinite(shape.sxy));
+
+    return shape;
+}
+
+// ellipse derotation (sx, sy, sxy) -> (major, minor, theta)
+psEllipseAxes psEllipseShapeToAxes(psEllipseShape shape, double maxAR)
+{
+    psEllipseAxes axes;
+
+    double f1 = 1.0 / PS_SQR(shape.sy) + 1.0 / PS_SQR(shape.sx);
+    double f2 = 1.0 / PS_SQR(shape.sy) - 1.0 / PS_SQR(shape.sx);
+    double f3 = sqrt(PS_SQR(f2) + 4*PS_SQR(shape.sxy));
+
+    axes.minor = sqrt (2.0 / (f1 + f3));
+    axes.theta = -0.5 * atan2 (+2.0*shape.sxy, f2);
+
+    // long, thin objects are likely to have a poorly measured major axis
+    // the angle and minor axis are likely to be ok.
+    // restrict the axis ratio
+    double rAR2 = (f1 - f3) / (f1 + f3);
+    if (rAR2 < 1.0/PS_SQR(maxAR)) {
+        axes.major = axes.minor * maxAR;
+    } else {
+        axes.major = sqrt (2.0 / (f1 - f3));
+    }
+
+    assert (isfinite(axes.theta));
+    assert (isfinite(axes.major));
+    assert (isfinite(axes.minor));
+
+    return axes;
+}
+
+// ellipse rotation (major, minor, theta) -> (e0, e1, e2)
 psEllipsePol psEllipseAxesToPol(psEllipseAxes axes)
 {
@@ -35,25 +156,5 @@
 }
 
-// ellipse rotation (major, minor, theta) -> (x2, y2, xy)
-psEllipsePol psEllipseShapeToPol(psEllipseShape shape)
-{
-    psEllipsePol pol;
-
-    double r = 1.0 / (1.0 - PS_SQR(shape.sx)*PS_SQR(shape.sy)*PS_SQR(shape.sxy));
-
-    pol.e0 = r*(PS_SQR(shape.sx) + PS_SQR(shape.sy));
-    pol.e1 = r*(PS_SQR(shape.sx) - PS_SQR(shape.sy));
-    // XXX I do not understand this negative sign
-    pol.e2 = -r*(2.0*PS_SQR(shape.sx)*PS_SQR(shape.sy)*shape.sxy);
-
-    assert (isfinite(pol.e0));
-    assert (isfinite(pol.e1));
-    assert (isfinite(pol.e2));
-
-    return pol;
-}
-
-// ellipse rotation (major, minor, theta) -> (x2, y2, xy)
-// XXXX handle case where e0 < LIMIT
+// ellipse rotation (e0, e1, e2) -> (major, minor, theta)
 psEllipseAxes psEllipsePolToAxes(const psEllipsePol pol,
 				 const float minMinorAxis)
@@ -96,87 +197,41 @@
 }
 
-// ellipse rotation (major, minor, theta) -> (x2, y2, xy)
-psEllipseMoments psEllipseAxesToMoments(psEllipseAxes axes)
-{
-    psEllipseMoments moments;
-
-    double f1 = PS_SQR(axes.major) + PS_SQR(axes.minor);
-    double f2 = PS_SQR(axes.major) - PS_SQR(axes.minor);
-
-    moments.x2 = +0.5*f1 + 0.5*f2*cos(2*axes.theta);
-    moments.y2 = +0.5*f1 - 0.5*f2*cos(2*axes.theta);
-    moments.xy = +0.5*f2*sin(2*axes.theta);
-
-    assert (isfinite(moments.x2));
-    assert (isfinite(moments.y2));
-    assert (isfinite(moments.xy));
-
-    return moments;
-}
-
-// ellipse rotation (x2, y2, xy) -> (major, minor, theta).  NOTE: major, minor are the
-// 1-sigma lengths in the major,minor directions assuming the moments represent a Gaussian
-// profile
-psEllipseAxes psEllipseMomentsToAxes(psEllipseMoments moments, double maxAR)
-{
-    psEllipseAxes axes;
-    psEllipseAxes badValue = {NAN, NAN, NAN};
-
-    if (!isfinite(moments.x2)) return badValue;
-    if (!isfinite(moments.y2)) return badValue;
-    if (!isfinite(moments.xy)) return badValue;
-
-    if (moments.x2 < 0) return badValue;
-    if (moments.y2 < 0) return badValue;
-
-    double g1 = moments.x2 + moments.y2;
-    double g2 = moments.x2 - moments.y2;
-    double g3 = sqrt(PS_SQR(g2) + 4*PS_SQR(moments.xy));
-
-    axes.major = sqrt (0.5*(g1 + g3));
-    axes.theta = +0.5 * atan2 (+2.0*moments.xy, g2); // theta in radians
-
-    // long, thin objects are likely to have a poorly measured minor axis
-    // the angle and major axis are likely to be ok.
-    // restrict the axis ratio
-    double rAR2 = (g1 - g3) / (g1 + g3);
-    if (rAR2 < 1.0/PS_SQR(maxAR)) {
-        axes.minor = axes.major / maxAR;
-    } else {
-        axes.minor = sqrt (0.5*(g1 - g3));
-    }
-
-    assert (isfinite(axes.major));
-    assert (isfinite(axes.minor));
-    assert (isfinite(axes.theta));
-
-    return axes;
-}
-
-// ellipse rotation (major, minor, theta) -> (sx, sy, sxy)
-// theta is postive rotation of major axis away from x-axis
-psEllipseShape psEllipseAxesToShape(psEllipseAxes axes)
+// ellipse rotation (sx, sy, sxy) -> (e0, e1, e2)
+psEllipsePol psEllipseShapeToPol(psEllipseShape shape)
+{
+    psEllipsePol pol;
+
+    double r = 1.0 / (1.0 - PS_SQR(shape.sx)*PS_SQR(shape.sy)*PS_SQR(shape.sxy));
+
+    pol.e0 = r*(PS_SQR(shape.sx) + PS_SQR(shape.sy));
+    pol.e1 = r*(PS_SQR(shape.sx) - PS_SQR(shape.sy));
+    // XXX I do not understand this negative sign
+    pol.e2 = -r*(2.0*PS_SQR(shape.sx)*PS_SQR(shape.sy)*shape.sxy);
+
+    assert (isfinite(pol.e0));
+    assert (isfinite(pol.e1));
+    assert (isfinite(pol.e2));
+
+    return pol;
+}
+
+// ellipse rotation (e0, e1, e2) -> (sx, sy, sxy)
+psEllipseShape psEllipsePolToShape(psEllipsePol pol)
 {
     psEllipseShape shape;
-    psEllipseShape badValue = {NAN, NAN, NAN};
-
-    if (!isfinite(axes.minor)) return badValue;
-    if (!isfinite(axes.major)) return badValue;
-    if (!isfinite(axes.theta)) return badValue;
-
-    if (axes.minor <= 0) return badValue;
-    if (axes.major <= 0) return badValue;
-
-    double f1 = 1.0 / PS_SQR(axes.minor) + 1.0 / PS_SQR(axes.major);
-    double f2 = 1.0 / PS_SQR(axes.minor) - 1.0 / PS_SQR(axes.major);
-
-    double sxr = 0.5*f1 - 0.5*f2*cos(2*axes.theta);
-    double syr = 0.5*f1 + 0.5*f2*cos(2*axes.theta);
-
-    // sxr, syr cannot be < 0 (f1 >= f2)
-
-    shape.sx  = +1.0 / sqrt(sxr);
-    shape.sy  = +1.0 / sqrt(syr);
-    shape.sxy = -0.5*f2*sin(2*axes.theta);
+
+    double q = sqrt (PS_SQR(pol.e1) + PS_SQR(pol.e2));
+    double p = PS_SQR(pol.e0) + PS_SQR(q) - 2.0*q*pol.e0;
+
+    // double f1 = 4*pol.e0 / p;
+    // double f2 = 4*q      / p;
+    
+    double sxr = 2.0*(pol.e0 - pol.e1) / p;
+    double syr = 2.0*(pol.e0 + pol.e1) / p;
+
+    shape.sx = sqrt(1.0 / sxr);
+    shape.sy = sqrt(1.0 / syr);
+
+    shape.sxy = -2.0 * pol.e2 / p;
 
     assert (isfinite(shape.sx));
@@ -187,33 +242,4 @@
 }
 
-// ellipse derotation (sx, sy, sxy) -> (major, minor, theta)
-psEllipseAxes psEllipseShapeToAxes(psEllipseShape shape, double maxAR)
-{
-    psEllipseAxes axes;
-
-    double f1 = 1.0 / PS_SQR(shape.sy) + 1.0 / PS_SQR(shape.sx);
-    double f2 = 1.0 / PS_SQR(shape.sy) - 1.0 / PS_SQR(shape.sx);
-    double f3 = sqrt(PS_SQR(f2) + 4*PS_SQR(shape.sxy));
-
-    axes.minor = sqrt (2.0 / (f1 + f3));
-    axes.theta = -0.5 * atan2 (+2.0*shape.sxy, f2);
-
-    // long, thin objects are likely to have a poorly measured major axis
-    // the angle and minor axis are likely to be ok.
-    // restrict the axis ratio
-    double rAR2 = (f1 - f3) / (f1 + f3);
-    if (rAR2 < 1.0/PS_SQR(maxAR)) {
-        axes.major = axes.minor * maxAR;
-    } else {
-        axes.major = sqrt (2.0 / (f1 - f3));
-    }
-
-    assert (isfinite(axes.theta));
-    assert (isfinite(axes.major));
-    assert (isfinite(axes.minor));
-
-    return axes;
-}
-
 // XXX keep this construction?
 // force the axis ratio to be less than 10
Index: trunk/psLib/src/math/psEllipse.h
===================================================================
--- trunk/psLib/src/math/psEllipse.h	(revision 36374)
+++ trunk/psLib/src/math/psEllipse.h	(revision 36375)
@@ -38,5 +38,5 @@
 } psEllipseShape;
 
-/// Ellipse defined in terms of polarisations
+/// Ellipse defined in terms of polarizations
 typedef struct {
     double e0;                          ///< Scale (Mxx + Myy)
@@ -65,18 +65,20 @@
                                    );
 
-/// Convert axes to polarisation representation
+/// Convert axes to polarization representation
 psEllipsePol psEllipseAxesToPol(psEllipseAxes axes ///< Axes of ellipse
                                 );
 
-/// Convert shape to polarisation representation
+/// Convert polarization to axes representation
+psEllipseAxes psEllipsePolToAxes(const psEllipsePol pol, ///< Polarization of ellipse
+				 const float minMinorAxis ///< Minimum allowed minor axis
+    );
+
+/// Convert shape to polarization representation
 psEllipsePol psEllipseShapeToPol(psEllipseShape shape ///< Shape of ellipse
                                  );
 
-/// Convert polarisation to axes representation
-///
-/// XXX This API goes against the PS convention of outputs being first.
-psEllipseAxes psEllipsePolToAxes(const psEllipsePol pol, ///< Polarisation of ellipse
-				 const float minMinorAxis ///< Minimum allowed minor axis
-    );
+/// Convert shape to polarization representation
+psEllipseShape psEllipsePolToShape(psEllipsePol pol ///< Shape of ellipse
+                                 );
 
 /// @}
Index: trunk/psLib/test/imageops/Makefile.am
===================================================================
--- trunk/psLib/test/imageops/Makefile.am	(revision 36374)
+++ trunk/psLib/test/imageops/Makefile.am	(revision 36375)
@@ -22,4 +22,5 @@
 	tap_psImageConvolve \
 	tap_psImageConvolve2 \
+	tap_psImageConvolve2dCache \
 	tap_psImagePixelExtract \
 	tap_psImageInterpolate2 \
Index: trunk/psLib/test/imageops/tap_psImageConvolve2dCache.c
===================================================================
--- trunk/psLib/test/imageops/tap_psImageConvolve2dCache.c	(revision 36375)
+++ trunk/psLib/test/imageops/tap_psImageConvolve2dCache.c	(revision 36375)
@@ -0,0 +1,140 @@
+/** @file  tap_psImageConvolve2dCache.c
+ *
+ *  @brief Contains the tests for psImageConvolve2dCache.[ch]
+ *  @author Eugene Magnier, IfA
+ *  @date $Date: 2007-05-01 00:08:52 $
+ *
+ *  Copyright 2013 IfA, University of Hawaii
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include "tap.h"
+#include "pstap.h"
+
+bool tap_convolve_delta (int Nx, int Ny, int Xo, int Yo, float Nsigma, float sigma, float kappa);
+
+int main(int argc, char **argv) {
+
+    plan_tests(141);
+
+    psMemId id = psMemGetId();
+
+    // generate an image to convolve
+    tap_convolve_delta (301, 301, 150, 150, 3.0, 2.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 200, 3.0, 2.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 100, 3.0, 2.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 200, 3.0, 2.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 100, 3.0, 2.0, 1.0);
+
+    tap_convolve_delta (301, 301, 150, 150, 3.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 200, 3.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 100, 3.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 200, 3.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 100, 3.0, 3.0, 1.0);
+
+    tap_convolve_delta (301, 301, 150, 150, 3.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 200, 3.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 100, 3.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 200, 3.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 100, 3.0, 1.0, 1.0);
+
+    tap_convolve_delta (301, 301, 150, 150, 3.0, 2.0, 2.0);
+    tap_convolve_delta (301, 301, 100, 200, 3.0, 2.0, 2.0);
+    tap_convolve_delta (301, 301, 200, 100, 3.0, 2.0, 2.0);
+    tap_convolve_delta (301, 301, 200, 200, 3.0, 2.0, 2.0);
+    tap_convolve_delta (301, 301, 100, 100, 3.0, 2.0, 2.0);
+
+    tap_convolve_delta (301, 301, 150, 150, 4.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 200, 4.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 100, 4.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 200, 4.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 100, 4.0, 3.0, 1.0);
+
+    tap_convolve_delta (301, 301, 150, 150, 5.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 200, 5.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 100, 5.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 200, 5.0, 3.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 100, 5.0, 3.0, 1.0);
+
+    tap_convolve_delta (301, 301, 150, 150, 5.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 200, 5.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 100, 5.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 200, 200, 5.0, 1.0, 1.0);
+    tap_convolve_delta (301, 301, 100, 100, 5.0, 1.0, 1.0);
+
+    ok(!psMemCheckLeaks (id, NULL, stdout, false), "no memory leaks");
+
+    return exit_status();
+}
+
+bool tap_convolve_delta (int Nx, int Ny, int Xo, int Yo, float Nsigma, float sigma, float kappa) {
+
+    bool status;
+
+    psImage *input = psImageAlloc (Nx, Ny, PS_TYPE_F32);
+    psImageInit (input, 0.0);
+    input->data.F32[Yo][Xo] = 1.0;
+
+    psImageSmooth2dCacheData *smdata = psImageSmooth2dCacheAlloc (Nsigma);
+    ok(smdata, "init smdata");
+    ok(!smdata->radflux, "init radflux to NULL");
+    ok(smdata->Nsigma == Nsigma, "init Nsigma");
+    
+    status = psImageSmooth2dCacheKernel_PS1_V1 (smdata, sigma, kappa);
+    ok(status, "generate a kernel");
+    
+    status = psImageSmooth2dCache_F32 (input, smdata);
+    ok(status, "smoothed image");
+    
+    double s = 0.0;
+
+    psImage *model = psImageAlloc (Nx, Ny, PS_TYPE_F32);
+    psImageInit (model, 0.0);
+    for (int iy = 0; iy < model->numRows; iy++) {
+	for (int ix = 0; ix < model->numCols; ix++) {
+	    float z = 0.5*PS_SQR((ix + 0.5 - Xo - 0.5) / sigma) + 0.5*PS_SQR((iy + 0.5 - Yo - 0.5) / sigma);
+	    if (z > 0.5*PS_SQR(smdata->Nsigma)) continue;
+	   
+	    float f = 1.0 / (1.0 + z*kappa + pow(z, 1.666));
+
+	    model->data.F32[iy][ix] = f;
+	    s += f;
+	}
+    }
+    for (int iy = 0; iy < model->numRows; iy++) {
+	for (int ix = 0; ix < model->numCols; ix++) {
+	    model->data.F32[iy][ix] /= s;
+	}
+    }
+
+    float min_df = +10.0;
+    float max_df = -10.0;
+    for (int iy = 0; iy < model->numRows; iy++) {
+	for (int ix = 0; ix < model->numCols; ix++) {
+	    float df = input->data.F32[iy][ix] - model->data.F32[iy][ix];
+	    min_df = PS_MIN (min_df, df);
+	    max_df = PS_MAX (max_df, df);
+	}
+    }
+    ok (fabs(min_df) < 3e-5, "minor deviations");
+    ok (fabs(max_df) < 3e-5, "minor deviations");
+
+    if ((fabs(min_df) >= 3e-5) || (fabs(max_df) >= 3e-5)) {
+	psFits *fits = NULL;
+	fits = psFitsOpen ("input.fits", "w");
+	psFitsWriteImage (fits, NULL, input, 0, NULL);
+	psFitsClose (fits);
+
+	fits = psFitsOpen ("model.fits", "w");
+	psFitsWriteImage (fits, NULL, model, 0, NULL);
+	psFitsClose (fits);
+    }
+
+    psFree (input);
+    psFree (model);
+    psFree (smdata);
+
+    return true;
+}
Index: trunk/psModules/src/camera/pmFPAfile.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.c	(revision 36374)
+++ trunk/psModules/src/camera/pmFPAfile.c	(revision 36375)
@@ -491,4 +491,7 @@
     if (!strcasecmp(type, "CMF"))     {
         return PM_FPA_FILE_CMF;
+    }
+    if (!strcasecmp(type, "CFF"))     {
+        return PM_FPA_FILE_CFF;
     }
     if (!strcasecmp(type, "WCS"))     {
Index: trunk/psModules/src/camera/pmFPAfile.h
===================================================================
--- trunk/psModules/src/camera/pmFPAfile.h	(revision 36374)
+++ trunk/psModules/src/camera/pmFPAfile.h	(revision 36375)
@@ -34,4 +34,5 @@
     PM_FPA_FILE_CMP,
     PM_FPA_FILE_CMF,
+    PM_FPA_FILE_CFF,
     PM_FPA_FILE_WCS,
     PM_FPA_FILE_RAW,
Index: trunk/psModules/src/camera/pmFPAfileDefine.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfileDefine.c	(revision 36374)
+++ trunk/psModules/src/camera/pmFPAfileDefine.c	(revision 36375)
@@ -103,7 +103,13 @@
 
     type = psMetadataLookupStr(&status, data, "FILE.TYPE");
+    if (!type) {
+        psError(PM_ERR_CONFIG, true, "FILE.TYPE is not defined for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+
     file->type = pmFPAfileTypeFromString(type);
     if (file->type == PM_FPA_FILE_NONE) {
-        psError(PM_ERR_CONFIG, true, "FILE.TYPE is not defined for %s\n", name);
+        psError(PM_ERR_CONFIG, true, "FILE.TYPE %s is not registered in pmFPAfile.c:pmFPAfileTypeFromString\n", type);
         psFree(file);
         return NULL;
Index: trunk/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- trunk/psModules/src/camera/pmFPAfileIO.c	(revision 36374)
+++ trunk/psModules/src/camera/pmFPAfileIO.c	(revision 36375)
@@ -222,4 +222,5 @@
       case PM_FPA_FILE_CMP:
       case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_CFF:
       case PM_FPA_FILE_WCS:
       case PM_FPA_FILE_SRCTEXT:
@@ -317,4 +318,5 @@
       case PM_FPA_FILE_CMP:
       case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_CFF:
       case PM_FPA_FILE_WCS:
       case PM_FPA_FILE_PSF:
@@ -483,4 +485,5 @@
       case PM_FPA_FILE_CMP:
       case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_CFF:
         status = pmFPAviewWriteObjects (view, file, config);
         break;
@@ -567,4 +570,5 @@
       case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_CFF:
       case PM_FPA_FILE_WCS:
       case PM_FPA_FILE_PSF:
@@ -643,4 +647,5 @@
       case PM_FPA_FILE_CMP:
       case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_CFF:
       case PM_FPA_FILE_WCS:
       case PM_FPA_FILE_PSF:
@@ -804,4 +809,5 @@
       case PM_FPA_FILE_PATTERN:
       case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_CFF:
       case PM_FPA_FILE_WCS:
       case PM_FPA_FILE_PSF:
@@ -1016,4 +1022,5 @@
       case PM_FPA_FILE_CMP:
       case PM_FPA_FILE_WCS:
+      case PM_FPA_FILE_CFF:
       case PM_FPA_FILE_JPEG:
       case PM_FPA_FILE_KAPA:
Index: trunk/psModules/src/config/pmConfig.c
===================================================================
--- trunk/psModules/src/config/pmConfig.c	(revision 36374)
+++ trunk/psModules/src/config/pmConfig.c	(revision 36375)
@@ -1860,5 +1860,5 @@
     }
 
-    return psMetadataLookupMetadata(NULL, filerules, realname);
+    return psMetadataLookupMetadata(&mdok, filerules, realname);
 }
 
Index: trunk/psModules/src/objects/Makefile.am
===================================================================
--- trunk/psModules/src/objects/Makefile.am	(revision 36374)
+++ trunk/psModules/src/objects/Makefile.am	(revision 36375)
@@ -40,4 +40,5 @@
 	pmSourceIO_SX.c \
 	pmSourceIO_CMP.c \
+	pmSourceIO_CFF.c \
 	pmSourceIO_SMPDATA.c \
 	pmSourceIO_PS1_DEV_0.c \
Index: trunk/psModules/src/objects/models/pmModel_DEV.c
===================================================================
--- trunk/psModules/src/objects/models/pmModel_DEV.c	(revision 36374)
+++ trunk/psModules/src/objects/models/pmModel_DEV.c	(revision 36375)
@@ -269,5 +269,6 @@
 
     // Mxx, Mxy, Myy define the elliptical shape, but Mrf defines the width 
-    float scale = moments->Mrf / axes.major;
+    // the factor of 2.3 comes from Table 1 of Graham and Driver (2005)
+    float scale = moments->Mrf / axes.major / 2.3;
     axes.major *= scale;
     axes.minor *= scale;
Index: trunk/psModules/src/objects/models/pmModel_EXP.c
===================================================================
--- trunk/psModules/src/objects/models/pmModel_EXP.c	(revision 36374)
+++ trunk/psModules/src/objects/models/pmModel_EXP.c	(revision 36375)
@@ -63,4 +63,17 @@
 // 0.5 PIX: the parameters are defined in terms of pixel coords, so the incoming pixcoords
 // values need to be pixel coords
+//
+
+// Notes on changing kappa value from 1.70056 to 1.678
+// I'm using a functional form f(x,y) = Io exp(-kappa (r / r_e)).  
+// The article by Graham & Driver (2005) uses a form Ie exp(-bn [(r / r_e) -1]) 
+// which is equal to Ie exp(-bn (r / r_e)) exp(bn).  
+// Thus, my Io = Ie exp(bn) and my kappa is their bn.
+// My value of kappa is 1.700, their value for bn is 1.678., so I am off by a small amount there (1.5%).  
+
+
+#define KAPPA_EXP 1.678
+#define OLD_KAPP_EXP 1.70056
+
 
 // Lax parameter limits
@@ -109,5 +122,5 @@
     // for EXP, we can hard-wire kappa(1):
     // float index = 1.0;
-    float kappa = 1.70056;
+    float kappa = KAPPA_EXP;
 
     // sqrt(z) is r
@@ -318,5 +331,5 @@
 
     // static value for EXP:
-    float kappa = 1.70056;
+    float kappa = KAPPA_EXP;
 
     // f = Io exp(-kappa*sqrt(z)) -> sqrt(z) = ln(Io/f) / kappa
Index: trunk/psModules/src/objects/models/pmModel_SERSIC.c
===================================================================
--- trunk/psModules/src/objects/models/pmModel_SERSIC.c	(revision 36374)
+++ trunk/psModules/src/objects/models/pmModel_SERSIC.c	(revision 36375)
@@ -75,5 +75,5 @@
 
 // Lax parameter limits
-static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.001, 0.001, -1.0, 0.1 };
+static float paramsMinLax[] = { -1.0e3, 1.0e-2, -100, -100, 0.001, 0.001, -1.0, 0.0625 };
 static float paramsMaxLax[] = { 1.0e5, 1.0e9, 1.0e5, 1.0e5, 100, 100, 1.0, 1.0 };
 
Index: trunk/psModules/src/objects/pmModel.c
===================================================================
--- trunk/psModules/src/objects/pmModel.c	(revision 36374)
+++ trunk/psModules/src/objects/pmModel.c	(revision 36375)
@@ -217,9 +217,11 @@
     // the options allow us to modify various aspects of the model
     if (mode & PM_MODEL_OP_NORM) {
+	// if we are including the sky, renormalizing should force use to normalized down the sky flux
+	params->data.F32[PM_PAR_SKY] /= params->data.F32[PM_PAR_I0];
         params->data.F32[PM_PAR_I0] = 1.0;
     }
     if (!(mode & PM_MODEL_OP_SKY)) {
         params->data.F32[PM_PAR_SKY] = 0.0;
-    }
+    } 
     if (mode & PM_MODEL_OP_CENTER) {
         params->data.F32[PM_PAR_XPOS] = image->col0 + 0.5*image->numCols;
Index: trunk/psModules/src/objects/pmModelFuncs.h
===================================================================
--- trunk/psModules/src/objects/pmModelFuncs.h	(revision 36374)
+++ trunk/psModules/src/objects/pmModelFuncs.h	(revision 36375)
@@ -36,11 +36,16 @@
 
 typedef enum {
-    PM_MODEL_STATUS_NONE         = 0x00, ///< model fit not yet attempted, no other info
-    PM_MODEL_STATUS_FITTED       = 0x01, ///< model fit completed
-    PM_MODEL_STATUS_NONCONVERGE  = 0x02, ///< model fit did not converge
-    PM_MODEL_STATUS_OFFIMAGE     = 0x04, ///< model fit drove out of range
-    PM_MODEL_STATUS_BADARGS      = 0x08, ///< model fit called with invalid args
-    PM_MODEL_STATUS_LIMITS       = 0x10, ///< model parameters hit limits
-    PM_MODEL_STATUS_WEAK_FIT     = 0x20, ///< model fit met loose tolerance, but not tight tolerance
+    PM_MODEL_STATUS_NONE           = 0x000, ///< model fit not yet attempted, no other info
+    PM_MODEL_STATUS_FITTED         = 0x001, ///< model fit completed
+    PM_MODEL_STATUS_NONCONVERGE    = 0x002, ///< model fit did not converge
+    PM_MODEL_STATUS_OFFIMAGE       = 0x004, ///< model fit drove out of range
+    PM_MODEL_STATUS_BADARGS        = 0x008, ///< model fit called with invalid args
+    PM_MODEL_STATUS_LIMITS         = 0x010, ///< model parameters hit limits
+    PM_MODEL_STATUS_WEAK_FIT       = 0x020, ///< model fit met loose tolerance, but not tight tolerance
+    PM_MODEL_STATUS_NAN_CHISQ      = 0x040, ///< model fit failed with a NAN chisq 
+    PM_MODEL_SERSIC_PCM_FAIL_GUESS = 0x080, ///< sersic model fit failed on the initial moments-based guess
+    PM_MODEL_SERSIC_PCM_FAIL_GRID  = 0x100, ///< sersic model fit failed on the grid search
+    PM_MODEL_PCM_FAIL_GUESS        = 0x200, ///< non-sersic model fit failed on the initial moments-based guess
+    PM_MODEL_BEST_FIT              = 0x400, ///< this model was the best fit and was subtracted
 } pmModelStatus;
 
Index: trunk/psModules/src/objects/pmModelUtils.c
===================================================================
--- trunk/psModules/src/objects/pmModelUtils.c	(revision 36374)
+++ trunk/psModules/src/objects/pmModelUtils.c	(revision 36375)
@@ -129,4 +129,8 @@
 bool pmModelAxesToParams (float *Sxx, float *Sxy, float *Syy, psEllipseAxes axes, bool useReff)  {
 
+    // restrict axex to 0.5 here not below 
+    if (axes.minor < 0.2) axes.minor = 0.2;
+    if (axes.major < 0.2) axes.major = 0.2;
+
     psEllipseShape shape = psEllipseAxesToShape (axes);
 
@@ -137,10 +141,14 @@
     // set the shape parameters
     if (useReff) {
-	*Sxx  = PS_MAX(0.5, shape.sx);
-	*Syy  = PS_MAX(0.5, shape.sy);
+	// *Sxx  = PS_MAX(0.5, shape.sx);
+	// *Syy  = PS_MAX(0.5, shape.sy);
+	*Sxx  = shape.sx;
+	*Syy  = shape.sy;
 	*Sxy  = shape.sxy * 2.0;
     } else {
-	*Sxx  = PS_MAX(0.5, M_SQRT2*shape.sx);
-	*Syy  = PS_MAX(0.5, M_SQRT2*shape.sy);
+	// *Sxx  = PS_MAX(0.5, M_SQRT2*shape.sx);
+	// *Syy  = PS_MAX(0.5, M_SQRT2*shape.sy);
+	*Sxx  = M_SQRT2*shape.sx;
+	*Syy  = M_SQRT2*shape.sy;
 	*Sxy  = shape.sxy;
     }
@@ -193,4 +201,5 @@
     if (!isfinite(axes.minor)) return false;
     if (!isfinite(axes.theta)) return false;
+    if (axes.major == 0) return false;
 
     // Mxx, Mxy, Myy define the elliptical shape, but Mrf defines the width 
Index: trunk/psModules/src/objects/pmModel_CentralPixel.c
===================================================================
--- trunk/psModules/src/objects/pmModel_CentralPixel.c	(revision 36374)
+++ trunk/psModules/src/objects/pmModel_CentralPixel.c	(revision 36375)
@@ -695,5 +695,6 @@
     int   npix = 0;
 
-    float kappa = -0.275552 + 1.972625*Sindex + 0.003487 * PS_SQR(Sindex);
+    // -0.275552 + 1.972625*Sindex + 0.003487 * PS_SQR(Sindex);
+    float kappa = pmSersicKappa (Sindex);
     float rindex = 0.5 / Sindex;
 
@@ -703,14 +704,21 @@
 
     float delta = 1.0 / (float) Nsub;
-    float off = -Nsub2 * delta;
-    for (float ix = off; ix < 0.5; ix += delta) {
-	for (float iy = off; iy < 0.5; iy += delta) {
-
-	    float dX = dx + ix;
-	    float dY = dy + iy;
+    // float off = -Nsub2 * delta;
+
+    int Sx = (int) floor(dx / delta);
+    int Sy = (int) floor(dy / delta);
+
+    for (int ix = -Nsub2; ix <= Nsub2; ix++) {
+      float dX = delta * (Sx + ix);
+      for (int iy = -Nsub2; iy <= Nsub2; iy++) {
+	float dY = delta * (Sy + iy);
 	    float z = PS_SQR(dX / Rxx) + PS_SQR(dY / Ryy) + dX * dY * Rxy;
 
 	    float q = pow (z, rindex);
 	    float f = exp(-kappa*q);
+
+	    // if ((ix == 0) && (iy == 0)) {
+	    //   // fprintf (stderr, "this: %f  %f  %f  --  full : %f %f\n", z, q, f, flux, (float) npix);
+	    // }
 
 	    flux += f;
Index: trunk/psModules/src/objects/pmPCM_MinimizeChisq.c
===================================================================
--- trunk/psModules/src/objects/pmPCM_MinimizeChisq.c	(revision 36374)
+++ trunk/psModules/src/objects/pmPCM_MinimizeChisq.c	(revision 36375)
@@ -135,4 +135,12 @@
 	}
 
+	if (min->isInteractive) {
+	    fprintf (stderr, "%d : ", min->iter);
+	    for (int ti = 0; ti < params->n; ti++) {
+		fprintf (stderr, "%f  ", params->data.F32[ti]);
+	    }
+	    fprintf (stderr, " : %f\n", min->value);
+	}
+
 	char key[10]; // used for interactive responses
 	bool testValue = false;
@@ -140,5 +148,5 @@
         // set a new guess for Alpha, Beta, Params
         if (!psMinLM_GuessABP(Alpha, Beta, Params, alpha, beta, params, paramMask, checkLimits, lambda, &dLinear)) {
-	    if (min->isInteractive) {
+	    if (false && min->isInteractive) {
 		fprintf (stdout, "guess failed (singular matrix or NaN values), continue? [Y,n] ");
 		if (!fgets(key, 8, stdin)) {
@@ -167,5 +175,5 @@
         }
 
-	if (min->isInteractive) {
+	if (false && min->isInteractive) {
             p_psVectorPrint(psTraceGetDestination(), Params, "current parameters: ");
 	    fprintf (stdout, "last chisq : %f\n", min->value);
@@ -473,10 +481,15 @@
 # else
     if (pcm->use1Dgauss) {
-	// do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
-	// * the model flux is not masked
-	// * threading takes place above this level
-	pcm->modelConvFlux = psImageCopy (pcm->modelConvFlux, pcm->modelFlux, pcm->modelFlux->type.type);
-	psImageSmooth_PreAlloc_F32 (pcm->modelConvFlux, pcm->smdata);
-	// psImageSmooth (pcm->modelConvFlux, pcm->sigma, pcm->nsigma);
+
+	if (USE_1D_CACHE) {
+	    // do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
+	    // * the model flux is not masked
+	    // * threading takes place above this level
+	    pcm->modelConvFlux = psImageCopy (pcm->modelConvFlux, pcm->modelFlux, pcm->modelFlux->type.type);
+	    psImageSmoothCache_F32 (pcm->modelConvFlux, pcm->smdata);
+	} else {
+	    pcm->modelConvFlux = psImageCopy (pcm->modelConvFlux, pcm->modelFlux, pcm->modelFlux->type.type);
+	    psImageSmooth2dCache_F32 (pcm->modelConvFlux, pcm->smdata2d);
+	}
     } else {
 	psImageConvolveKernel (pcm->modelConvFlux, pcm->modelFlux, NULL, 0, pcm->psfFFT);
@@ -493,10 +506,14 @@
 # else
 	if (pcm->use1Dgauss) {
-	    // do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
-	    // * the model flux is not masked
-	    // * threading takes place above this level
-	    dmodelConv = psImageCopy (dmodelConv, dmodel, dmodel->type.type);
-	    psImageSmooth_PreAlloc_F32 (dmodelConv, pcm->smdata);
-	    // psImageSmooth (dmodelConv, pcm->sigma, pcm->nsigma);
+	    if (USE_1D_CACHE) {
+		// do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
+		// * the model flux is not masked
+		// * threading takes place above this level
+		dmodelConv = psImageCopy (dmodelConv, dmodel, dmodel->type.type);
+		psImageSmoothCache_F32 (dmodelConv, pcm->smdata);
+	    } else {
+		dmodelConv = psImageCopy (dmodelConv, dmodel, dmodel->type.type);
+		psImageSmooth2dCache_F32 (dmodelConv, pcm->smdata2d);
+	    }
 	} else {
 	    psImageConvolveKernel (dmodelConv, dmodel, NULL, 0, pcm->psfFFT);
@@ -514,10 +531,14 @@
 
 	if (pcm->use1Dgauss) {
-	    // do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
-	    // * the model flux is not masked
-	    // * threading takes place above this level
-	    dmodelConv = psImageCopy (dmodelConv, dmodel, dmodel->type.type);
-	    psImageSmooth_PreAlloc_F32 (dmodelConv, pcm->smdata);
-	    // psImageSmooth (dmodelConv, pcm->sigma, pcm->nsigma);
+	    if (USE_1D_CACHE) {
+		// do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
+		// * the model flux is not masked
+		// * threading takes place above this level
+		dmodelConv = psImageCopy (dmodelConv, dmodel, dmodel->type.type);
+		psImageSmoothCache_F32 (dmodelConv, pcm->smdata);
+	    } else {
+		dmodelConv = psImageCopy (dmodelConv, dmodel, dmodel->type.type);
+		psImageSmooth2dCache_F32 (dmodelConv, pcm->smdata2d);
+	    }
 	} else {
 	    psImageConvolveFFT (dmodelConv, dmodel, NULL, 0, pcm->psf);
@@ -541,5 +562,7 @@
     static int Npass = 0;
     char name[128]; 
-    snprintf (name, 128, "psf.%03d.fits", Npass); psphotSaveImage (NULL, pcm->psf->image, name);
+    if (!pcm->use1Dgauss) {
+      snprintf (name, 128, "psf.%03d.fits", Npass); psphotSaveImage (NULL, pcm->psf->image, name);
+    }
     snprintf (name, 128, "mod.%03d.fits", Npass); psphotSaveImage (NULL, pcm->modelFlux, name);
     snprintf (name, 128, "cnv.%03d.fits", Npass); psphotSaveImage (NULL, pcm->modelConvFlux, name);
@@ -579,5 +602,7 @@
 
             float ymodel  = pcm->modelConvFlux->data.F32[i][j];
-            float yweight = 1.0 / source->variance->data.F32[i][j];
+
+	    // XXXX note this point here:::
+            float yweight = pcm->poissonErrors ? 1.0 / source->variance->data.F32[i][j] : 1.0;
             float delta = ymodel - source->pixels->data.F32[i][j];
 
Index: trunk/psModules/src/objects/pmPCMdata.c
===================================================================
--- trunk/psModules/src/objects/pmPCMdata.c	(revision 36374)
+++ trunk/psModules/src/objects/pmPCMdata.c	(revision 36375)
@@ -43,5 +43,4 @@
 
 # define USE_DELTA_PSF 0
-# define USE_1D_GAUSS 1
 
 static void pmPCMdataFree (pmPCMdata *pcm) {
@@ -58,5 +57,7 @@
     psFree (pcm->psfFFT);
     psFree (pcm->constraint);
+
     psFree (pcm->smdata); // pre-allocated data for psImageSmooth_PreAlloc
+    psFree (pcm->smdata2d); // pre-allocated data for psImageSmooth_PreAlloc
     return;
 }
@@ -88,4 +89,7 @@
     }
 
+    pcm->smdata = NULL;
+    pcm->smdata2d = NULL;
+
     pcm->modelConv = NULL;
     pcm->psf = NULL;
@@ -94,8 +98,10 @@
     pcm->nDOF = 0;
 
+    pcm->poissonErrors = true;
+
     // full convolution with the PSF is expensive.  if we have to save time, we can do a 1D
     // convolution with a Gaussian approximation to the kernel
     pcm->use1Dgauss = false;
-    pcm->nsigma = 3.0; 
+    pcm->nsigma = NAN; // this is set to something defined by the user
     pcm->sigma = 1.0; // this should be set to something sensible when the psf is known
 
@@ -248,21 +254,115 @@
 }
 
+static int modelType_GAUSS = -1;
+static int modelType_PS1_V1 = -1;
+
+// generate a Gaussian smoothing kernel for supplied sigma.  sigma here does not need to match
+// that used to allocate the structure, but it is recommended
+bool psImageSmoothCacheKernel_PS1_V1 (psImageSmoothCacheData *smdata, float sigma, float kappa) {
+    // check for NULL structure elements?
+
+    int size = smdata->Nrange;
+
+    psFree (smdata->kernel);
+    smdata->kernel = psVectorAlloc(2 * smdata->Nrange + 1, PS_TYPE_F32);
+
+    double sum = 0.0;			// Sum of Gaussian, for normalization
+    double factor = 1.0 / (sigma * M_SQRT2);	// Multiplier for i -> z
+
+    // PS1_V1 is a power-law with fitted linear term:
+    // 1 / (1 + kappa z + z^1.666)  where z = (r/sigma)^2
+
+    // generate the kernel (not normalized)
+    for (int i = -size, j = 0; i <= size; i++, j++) {
+	float z = PS_SQR(i * factor);
+        sum += smdata->kernel->data.F32[j] = 1.0 / (1 + kappa * z + pow(z,1.666));
+    }
+
+    // renormalize kernel to integral of 1.0
+    for (int i = 0; i < 2 * size + 1; i++) {
+        smdata->kernel->data.F32[i] /= sum;
+    }
+
+    return true;
+}
+
+psImageSmoothCacheData *psImageSmoothCacheSetKernel (float *sigma, float *kappa, float nsigma, psImage *flux, pmModel *modelPSF) {
+
+    psAssert (modelPSF, "psf model must be defined");
+    
+    psEllipseAxes axes;
+    bool useReff = pmModelUseReff (modelPSF->type);
+    psF32 *PAR = modelPSF->params->data.F32;
+    pmModelParamsToAxes (&axes, PAR[PM_PAR_SXX], PAR[PM_PAR_SXY], PAR[PM_PAR_SYY], useReff);
+    
+    *sigma = NAN;
+    *kappa = NAN;
+
+    // XXX need to do this more carefully
+    if (modelPSF->type == modelType_GAUSS) {
+	float FWHM_MAJOR = 2*modelPSF->modelRadius (modelPSF->params, 0.5*PAR[PM_PAR_I0]);
+	float FWHM_MINOR = FWHM_MAJOR * (axes.minor / axes.major);
+	*sigma = 0.50 * (FWHM_MAJOR + FWHM_MINOR) / 2.35;
+    }
+    if (modelPSF->type == modelType_PS1_V1) {
+	*sigma = 0.5 * (axes.major + axes.minor);
+	*kappa = PAR[PM_PAR_7];
+    }
+    psAssert (isfinite(*sigma), "invalid model type");
+
+    // psImageSmoothCacheAlloc generates a structure but does not assign the smoothing vector
+    psImageSmoothCacheData *smdata = psImageSmoothCacheAlloc (flux, *sigma, nsigma);
+
+    if (modelPSF->type == modelType_GAUSS) {
+	psImageSmoothCacheKernel_Gauss (smdata, *sigma);
+    }
+    if (modelPSF->type == modelType_PS1_V1) {
+	psImageSmoothCacheKernel_PS1_V1 (smdata, *sigma, *kappa);
+    }
+
+    return smdata;
+}
+
+psImageSmooth2dCacheData *psImageSmooth2dCacheSetKernel (float *sigma, float *kappa, float nsigma, psImage *flux, pmModel *modelPSF) {
+
+    psAssert (modelPSF, "psf model must be defined");
+    
+    psEllipseAxes axes;
+    bool useReff = pmModelUseReff (modelPSF->type);
+    psF32 *PAR = modelPSF->params->data.F32;
+    pmModelParamsToAxes (&axes, PAR[PM_PAR_SXX], PAR[PM_PAR_SXY], PAR[PM_PAR_SYY], useReff);
+    
+    *sigma = NAN;
+    *kappa = NAN;
+
+    // XXX need to do this more carefully
+    if (modelPSF->type == modelType_GAUSS) {
+	float FWHM_MAJOR = 2*modelPSF->modelRadius (modelPSF->params, 0.5*PAR[PM_PAR_I0]);
+	float FWHM_MINOR = FWHM_MAJOR * (axes.minor / axes.major);
+	*sigma = 0.50 * (FWHM_MAJOR + FWHM_MINOR) / 2.35;
+    }
+    if (modelPSF->type == modelType_PS1_V1) {
+	*sigma = 0.5 * (axes.major + axes.minor);
+	*kappa = PAR[PM_PAR_7];
+    }
+    psAssert (isfinite(*sigma), "invalid model type");
+
+    // psImageSmoothCacheAlloc generates a structure but does not assign the smoothing vector
+    psImageSmooth2dCacheData *smdata = psImageSmooth2dCacheAlloc (nsigma);
+
+    if (modelPSF->type == modelType_GAUSS) {
+	psImageSmooth2dCacheKernel_Gauss (smdata, *sigma);
+    }
+    if (modelPSF->type == modelType_PS1_V1) {
+	psImageSmooth2dCacheKernel_PS1_V1 (smdata, *sigma, *kappa);
+    }
+
+    return smdata;
+}
+
 pmPCMdata *pmPCMinit(pmSource *source, pmSourceFitOptions *fitOptions, pmModel *model, psImageMaskType maskVal, float psfSize) {
 
-    // make sure we save a cached copy of the psf flux
-    pmSourceCachePSF (source, maskVal);
-
-    // convert the cached cached psf model for this source to a psKernel
-    psKernel *psf = pmPCMkernelFromPSF (source, psfSize);
-    if (!psf) {
-	// NOTE: this only happens if the source is too close to an edge
-        model->flags |= PM_MODEL_STATUS_BADARGS;
-	return NULL;
-    }
-
-# if (USE_DELTA_PSF)
-    psImageInit (psf->image, 0.0);
-    psf->image->data.F32[(int)(0.5*psf->image->numRows)][(int)(0.5*psf->image->numCols)] = 1.0;
-# endif
+    modelType_GAUSS = pmModelClassGetType ("PS_MODEL_GAUSS");
+    modelType_PS1_V1 = pmModelClassGetType ("PS_MODEL_PS1_V1");
 
     // count the number of unmasked pixels:
@@ -298,5 +398,4 @@
     if (nPix <  nParams + 1) {
         psTrace ("psModules.objects", 4, "insufficient valid pixels\n");
-	psFree (psf);
 	psFree (constraint);
         model->flags |= PM_MODEL_STATUS_BADARGS;
@@ -306,8 +405,9 @@
     // generate PCM data storage structure
     pmPCMdata *pcm = pmPCMdataAlloc (params, constraint->paramMask, source);
-
-    pcm->psf = psf;
     pcm->modelConv = psMemIncrRefCounter(model);
     pcm->constraint = constraint;
+
+    pcm->poissonErrors = fitOptions->poissonErrors;
+    pcm->nsigma = fitOptions->nsigma;
 
     pcm->nPix = nPix;
@@ -316,21 +416,29 @@
 
 # if (USE_1D_GAUSS)
-    pmModel *modelPSF = source->modelPSF;
-    psAssert (modelPSF, "psf model must be defined");
-    
-    psEllipseAxes axes;
-    bool useReff = pmModelUseReff (modelPSF->type);
-    psF32 *PAR = modelPSF->params->data.F32;
-    pmModelParamsToAxes (&axes, PAR[PM_PAR_SXX], PAR[PM_PAR_SXY], PAR[PM_PAR_SYY], useReff);
-    
-    float FWHM_MAJOR = 2*modelPSF->modelRadius (modelPSF->params, 0.5*PAR[PM_PAR_I0]);
-    float FWHM_MINOR = FWHM_MAJOR * (axes.minor / axes.major);
 
     pcm->use1Dgauss = true;
-    pcm->sigma = 0.5 * (FWHM_MAJOR + FWHM_MINOR) / 2.35;
-    pcm->nsigma = 2.0;
-
-    pcm->smdata = psImageSmooth_PreAlloc_DataAlloc (source->pixels, pcm->sigma, pcm->nsigma);
+    if (USE_1D_CACHE) {
+	pcm->smdata = psImageSmoothCacheSetKernel (&pcm->sigma, &pcm->kappa, pcm->nsigma, source->pixels, source->modelPSF);
+    } else {
+	pcm->smdata2d = psImageSmooth2dCacheSetKernel (&pcm->sigma, &pcm->kappa, pcm->nsigma, source->pixels, source->modelPSF);
+    }
+
 # else
+    // make sure we save a cached copy of the psf flux
+    pmSourceCachePSF (source, maskVal);
+
+    // convert the cached cached psf model for this source to a psKernel
+    psKernel *psf = pmPCMkernelFromPSF (source, psfSize);
+    if (!psf) {
+	// NOTE: this only happens if the source is too close to an edge
+        model->flags |= PM_MODEL_STATUS_BADARGS;
+	return NULL;
+    }
+
+# if (USE_DELTA_PSF)
+    psImageInit (psf->image, 0.0);
+    psf->image->data.F32[(int)(0.5*psf->image->numRows)][(int)(0.5*psf->image->numCols)] = 1.0;
+# endif
+    pcm->psf = psf;
     pcm->smdata = NULL;
 # endif
@@ -395,6 +503,29 @@
 	    pcm->dmodelsConvFlux->data[n] = psImageCopy (pcm->dmodelsConvFlux->data[n], source->pixels, PS_TYPE_F32);
 	}
-	psFree(pcm->smdata);
-	pcm->smdata = psImageSmooth_PreAlloc_DataAlloc (source->pixels, pcm->sigma, pcm->nsigma);
+
+	// If we have changed the window, we need to redefine the smoothing target vectors (but pcm->sigma,kappa,nsigma remain)
+	if (USE_1D_CACHE) {
+	    psFree(pcm->smdata);
+	    pcm->smdata = psImageSmoothCacheAlloc (source->pixels, pcm->sigma, pcm->nsigma);
+
+	    pmModel *modelPSF = source->modelPSF;
+	    if (modelPSF->type == modelType_GAUSS) {
+		psImageSmoothCacheKernel_Gauss (pcm->smdata, pcm->sigma);
+	    }
+	    if (modelPSF->type == modelType_PS1_V1) {
+		psImageSmoothCacheKernel_PS1_V1 (pcm->smdata, pcm->sigma, pcm->kappa);
+	    }
+	} else {
+	    psFree(pcm->smdata2d);
+	    pcm->smdata2d = psImageSmooth2dCacheAlloc (pcm->nsigma);
+
+	    pmModel *modelPSF = source->modelPSF;
+	    if (modelPSF->type == modelType_GAUSS) {
+		// psImageSmooth2dCacheKernel_Gauss (pcm->smdata2d, pcm->sigma);
+	    }
+	    if (modelPSF->type == modelType_PS1_V1) {
+		psImageSmooth2dCacheKernel_PS1_V1 (pcm->smdata2d, pcm->sigma, pcm->kappa);
+	    }
+	}
     }
 
@@ -403,5 +534,5 @@
 
 // construct a realization of the source model
-bool pmPCMCacheModel (pmSource *source, psImageMaskType maskVal, int psfSize) {
+bool pmPCMCacheModel (pmSource *source, psImageMaskType maskVal, int psfSize, float nsigma) {
 
     PS_ASSERT_PTR_NON_NULL(source, false);
@@ -420,24 +551,18 @@
     // convolve the model image with the PSF
     if (USE_1D_GAUSS) {
-	// do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
-	// * the model flux is not masked
-	// * threading takes place above this level
 	
-	// define the Gauss parameters from the psf
-	pmModel *modelPSF = source->modelPSF;
-	psAssert (modelPSF, "psf model must be defined");
-    
-	psEllipseAxes axes;
-	bool useReff = pmModelUseReff (modelPSF->type);
-	psF32 *PAR = modelPSF->params->data.F32;
-	pmModelParamsToAxes (&axes, PAR[PM_PAR_SXX], PAR[PM_PAR_SXY], PAR[PM_PAR_SYY], useReff);
-    
-	float FWHM_MAJOR = 2*modelPSF->modelRadius (modelPSF->params, 0.5*PAR[PM_PAR_I0]);
-	float FWHM_MINOR = FWHM_MAJOR * (axes.minor / axes.major);
-
-	float sigma = 0.5 * (FWHM_MAJOR + FWHM_MINOR) / 2.35;
-	float nsigma = 2.0;
-
-	psImageSmooth (source->modelFlux, sigma, nsigma);
+	float sigma = NAN;
+	float kappa = NAN;
+
+	if (USE_1D_CACHE) {
+	    psImageSmoothCacheData *smdata = psImageSmoothCacheSetKernel (&sigma, &kappa, nsigma, source->modelFlux, source->modelPSF);
+	    psImageSmoothCache_F32 (source->modelFlux, smdata);
+	    psFree (smdata);
+	} else {
+	    psImageSmooth2dCacheData *smdata = psImageSmooth2dCacheSetKernel (&sigma, &kappa, nsigma, source->modelFlux, source->modelPSF);
+	    psImageSmooth2dCache_F32 (source->modelFlux, smdata);
+	    psFree (smdata);
+	}
+	// old call: psImageSmooth (source->modelFlux, sigma, nsigma);
     } else {
 	// make sure we save a cached copy of the psf flux
@@ -459,5 +584,5 @@
 
 // construct a realization of the source model
-bool pmPCMMakeModel (pmSource *source, pmModel *model, psImageMaskType maskVal, int psfSize) {
+bool pmPCMMakeModel (pmSource *source, pmModel *model, float Nsigma, psImageMaskType maskVal, int psfSize) {
 
     PS_ASSERT_PTR_NON_NULL(source, false);
@@ -468,28 +593,23 @@
 
     // modelFlux always has unity normalization (I0 = 1.0)
-    pmModelAdd (source->modelFlux, source->maskObj, model, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
+    // pmModelAdd (source->modelFlux, source->maskObj, model, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
+    pmModelAdd (source->modelFlux, NULL, model, PM_MODEL_OP_FULL | PM_MODEL_OP_SKY | PM_MODEL_OP_NORM, maskVal);
 
     // convolve the model image with the PSF
     if (USE_1D_GAUSS) {
-	// do not use the threaded, mask-aware version of this code (psImageSmoothMaskPixelsThread):
-	// * the model flux is not masked
-	// * threading takes place above this level
-	
-	// define the Gauss parameters from the psf
-	pmModel *modelPSF = source->modelPSF;
-	psAssert (modelPSF, "psf model must be defined");
-    
-	psEllipseAxes axes;
-	bool useReff = pmModelUseReff (modelPSF->type);
-	psF32 *PAR = modelPSF->params->data.F32;
-	pmModelParamsToAxes (&axes, PAR[PM_PAR_SXX], PAR[PM_PAR_SXY], PAR[PM_PAR_SYY], useReff);
-    
-	float FWHM_MAJOR = 2*modelPSF->modelRadius (modelPSF->params, 0.5*PAR[PM_PAR_I0]);
-	float FWHM_MINOR = FWHM_MAJOR * (axes.minor / axes.major);
-
-	float sigma = 0.5 * (FWHM_MAJOR + FWHM_MINOR) / 2.35;
-	float nsigma = 2.0;
-
-	psImageSmooth (source->modelFlux, sigma, nsigma);
+
+	float sigma = NAN;
+	float kappa = NAN;
+
+	if (USE_1D_CACHE) {
+	    psImageSmoothCacheData *smdata = psImageSmoothCacheSetKernel (&sigma, &kappa, Nsigma, source->modelFlux, source->modelPSF);
+	    psImageSmoothCache_F32 (source->modelFlux, smdata);
+	    psFree (smdata);
+	} else {
+	    psImageSmooth2dCacheData *smdata = psImageSmooth2dCacheSetKernel (&sigma, &kappa, Nsigma, source->modelFlux, source->modelPSF);
+	    psImageSmooth2dCache_F32 (source->modelFlux, smdata);
+	    psFree (smdata);
+	}
+	// old call: psImageSmooth (source->modelFlux, sigma, nsigma);
     } else {
 	// make sure we save a cached copy of the psf flux
@@ -509,3 +629,2 @@
     return true;
 }
-
Index: trunk/psModules/src/objects/pmPCMdata.h
===================================================================
--- trunk/psModules/src/objects/pmPCMdata.h	(revision 36374)
+++ trunk/psModules/src/objects/pmPCMdata.h	(revision 36375)
@@ -14,4 +14,8 @@
 /// @addtogroup Objects Object Detection / Analysis Functions
 /// @{
+
+// XXX this is basically for testing -- when I am happy with the convolution process, I'll strip this out
+# define USE_1D_CACHE 0
+# define USE_1D_GAUSS 1
 
 /** pmPCMdata : PSF Convolved Model data storage structure
@@ -36,9 +40,14 @@
     int nDOF;
 
+    bool poissonErrors;
+
     bool use1Dgauss;
+    float kappa;
     float sigma;
     float nsigma;
 
-    psImageSmooth_PreAlloc_Data *smdata;
+    // psArray *smdata;
+    psImageSmoothCacheData *smdata;
+    psImageSmooth2dCacheData *smdata2d;
 } pmPCMdata;
 
@@ -96,7 +105,7 @@
 bool pmSourceFitPCM (pmPCMdata *pcm, pmSource *source, pmSourceFitOptions *fitOptions, psImageMaskType maskVal, psImageMaskType markVal, int psfSize);
 
-bool pmPCMCacheModel (pmSource *source, psImageMaskType maskVal, int psfSize);
+bool pmPCMCacheModel (pmSource *source, psImageMaskType maskVal, int psfSize, float nsigma);
 
-bool pmPCMMakeModel (pmSource *source, pmModel *model, psImageMaskType maskVal, int psfSize);
+bool pmPCMMakeModel (pmSource *source, pmModel *model, float Nsigma, psImageMaskType maskVal, int psfSize);
 
 /// @}
Index: trunk/psModules/src/objects/pmSource.c
===================================================================
--- trunk/psModules/src/objects/pmSource.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSource.c	(revision 36375)
@@ -66,4 +66,5 @@
     psFree(tmp->extpars);
     psFree(tmp->diffStats);
+    psFree(tmp->galaxyFits);
     psFree(tmp->radialAper);
     psTrace("psModules.objects", 10, "---- end ----\n");
@@ -164,4 +165,5 @@
     source->extpars = NULL;
     source->diffStats = NULL;
+    source->galaxyFits = NULL;
     source->radialAper = NULL;
     source->parent = NULL;
@@ -690,5 +692,4 @@
             // why do we recalculate moments here?
 	    // we already attempt to do this in psphotSourceStats
-            // pmSourceMoments (source, INNER_RADIUS);
             Nsatstar ++;
             continue;
@@ -804,191 +805,4 @@
     return true;
 }
-
-/******************************************************************************
-pmSourceMoments(source, radius): this function takes a subImage defined in the
-pmSource data structure, along with the peak location, and determines the
-various moments associated with that peak.
-
-Requires the following to have been created:
-    pmSource
-    pmSource->peak
-    pmSource->pixels
-    pmSource->variance
-    pmSource->mask
-
-XXX: The peak calculations are done in image coords, not subImage coords.
-
-XXX EAM : this version clips input pixels on S/N
-XXX EAM : this version returns false for several reasons
-*****************************************************************************/
-# define VALID_RADIUS(X,Y,RAD2) (((RAD2) >= (PS_SQR(X) + PS_SQR(Y))) ? 1 : 0)
-
-/*** this been moved to pmSourceMoments.c ***/
-# if (0)
-bool pmSourceMoments(pmSource *source,
-                     psF32 radius)
-{
-    psTrace("psModules.objects", 10, "---- begin ----\n");
-    PS_ASSERT_PTR_NON_NULL(source, false);
-    PS_ASSERT_PTR_NON_NULL(source->peak, false);
-    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
-    PS_ASSERT_FLOAT_LARGER_THAN(radius, 0.0, false);
-
-    //
-    // XXX: Verify the setting for sky if source->moments == NULL.
-    //
-    psF32 sky = 0.0;
-    if (source->moments == NULL) {
-        source->moments = pmMomentsAlloc();
-    } else {
-        sky = source->moments->Sky;
-    }
-
-    //
-    // Sum = SUM (z - sky)
-    // X1  = SUM (x - xc)*(z - sky)
-    // X2  = SUM (x - xc)^2 * (z - sky)
-    // XY  = SUM (x - xc)*(y - yc)*(z - sky)
-    //
-    psF32 peakPixel = -PS_MAX_F32;
-    psS32 numPixels = 0;
-    psF32 Sum = 0.0;
-    psF32 Var = 0.0;
-    psF32 X1 = 0.0;
-    psF32 Y1 = 0.0;
-    psF32 X2 = 0.0;
-    psF32 Y2 = 0.0;
-    psF32 XY = 0.0;
-    psF32 x  = 0;
-    psF32 y  = 0;
-    psF32 R2 = PS_SQR(radius);
-
-    psF32 xPeak = source->peak->x;
-    psF32 yPeak = source->peak->y;
-    psF32 xOff = source->pixels->col0 - source->peak->x;
-    psF32 yOff = source->pixels->row0 - source->peak->y;
-
-    // XXX why do I get different results for these two methods of finding Sx?
-    // XXX Sx, Sy would be better measured if we clip pixels close to sky
-    // XXX Sx, Sy can still be imaginary, so we probably need to keep Sx^2?
-    // We loop through all pixels in this subimage (source->pixels), and for each
-    // pixel that is not masked, AND within the radius of the peak pixel, we
-    // proceed with the moments calculation.  need to do two loops for a
-    // numerically stable result.  first loop: get the sums.
-    // XXX EAM : mask == 0 is valid
-
-    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
-
-        psF32 *vPix = source->pixels->data.F32[row];
-        psF32 *vWgt = source->variance->data.F32[row];
-        psImageMaskType *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[row];
-
-        for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
-            if (vMsk) {
-                if (*vMsk) {
-                    vMsk++;
-                    psTrace("psModules.objects", 10, "Ignoring pixel %d,%d due to mask: %d\n",
-                            col, row, (int)*vMsk);
-                    continue;
-                }
-                vMsk++;
-            }
-            if (isnan(*vPix)) continue;
-
-            psF32 xDiff = col + xOff;
-            psF32 yDiff = row + yOff;
-
-            // radius is just a function of (xDiff, yDiff)
-            if (!VALID_RADIUS(xDiff, yDiff, R2)) {
-#if 1
-                psTrace("psModules.objects", 10, "Ignoring pixel %d,%d due to position: %f %f\n",
-                        col, row, xDiff, yDiff);
-#endif
-                continue;
-            }
-
-            psF32 pDiff = *vPix - sky;
-            psF32 wDiff = *vWgt;
-
-            // XXX EAM : check for valid S/N in pixel
-            // XXX EAM : should this limit be user-defined?
-#if 1
-            if (PS_SQR(pDiff) < wDiff) {
-                psTrace("psModules.objects", 10, "Ignoring pixel %d,%d due to insignificance: %f, %f\n",
-                        col, row, pDiff, wDiff);
-                continue;
-            }
-#endif
-
-            Var += wDiff;
-            Sum += pDiff;
-
-            psF32 xWght = xDiff * pDiff;
-            psF32 yWght = yDiff * pDiff;
-
-            X1  += xWght;
-            Y1  += yWght;
-
-            XY  += xDiff * yWght;
-            X2  += xDiff * xWght;
-            Y2  += yDiff * yWght;
-
-            peakPixel = PS_MAX (*vPix, peakPixel);
-            numPixels++;
-        }
-    }
-
-    // if we have less than (1/4) of the possible pixels, force a retry
-    // XXX EAM - the limit is a bit arbitrary.  make it user defined?
-    if ((numPixels < 0.75*R2) || (Sum <= 0)) {
-        psTrace ("psModules.objects", 3, "insufficient valid pixels (%d vs %d; %f) for source\n",
-                 numPixels, (int)(0.75*R2), Sum);
-        psTrace("psModules.objects", 10, "---- end (false) ----\n");
-        return (false);
-    }
-
-    psTrace ("psModules.objects", 4, "sky: %f  Sum: %f  X1: %f  Y1: %f  X2: %f  Y2: %f  XY: %f  Npix: %d\n",
-             sky, Sum, X1, Y1, X2, Y2, XY, numPixels);
-
-    //
-    // first moment X  = X1/Sum + xc
-    // second moment X = sqrt (X2/Sum - (X1/Sum)^2)
-    // Sxy             = XY / Sum
-    //
-    x = X1/Sum;
-    y = Y1/Sum;
-    if ((fabs(x) > radius) || (fabs(y) > radius)) {
-        psTrace ("psModules.objects", 3, "large centroid swing; invalid peak %d, %d\n",
-                 source->peak->x, source->peak->y);
-        psTrace("psModules.objects", 10, "---- end(false)  ----\n");
-        return (false);
-    }
-
-    source->moments->Mx = x + xPeak;
-    source->moments->My = y + yPeak;
-
-    // XXX EAM : Sxy needs to have x*y subtracted
-    source->moments->Mxy = XY/Sum - x*y;
-    source->moments->Sum = Sum;
-    source->moments->SN  = Sum / sqrt(Var);
-    source->moments->Peak = peakPixel;
-    source->moments->nPixels = numPixels;
-
-    // XXX EAM : these values can be negative, so we need to limit the range
-    // XXX EAM : make the use of this consistent: should this be the second moment or sqrt?
-    // source->moments->Mxx = sqrt(PS_MAX(X2/Sum - PS_SQR(x), 0));
-    // source->moments->Myy = sqrt(PS_MAX(Y2/Sum - PS_SQR(y), 0));
-    source->moments->Mxx = PS_MAX(X2/Sum - PS_SQR(x), 0);
-    source->moments->Myy = PS_MAX(Y2/Sum - PS_SQR(y), 0);
-
-    psTrace ("psModules.objects", 4,
-             "sky: %f  Sum: %f  Mx: %f  My: %f  Mxx: %f  Myy: %f  Mxy: %f\n",
-             sky, Sum, source->moments->Mx, source->moments->My,
-             source->moments->Mxx, source->moments->Myy, source->moments->Mxy);
-
-    psTrace("psModules.objects", 10, "---- end ----\n");
-    return(true);
-}
-# endif
 
 // construct a realization of the source model
Index: trunk/psModules/src/objects/pmSource.h
===================================================================
--- trunk/psModules/src/objects/pmSource.h	(revision 36374)
+++ trunk/psModules/src/objects/pmSource.h	(revision 36375)
@@ -40,4 +40,6 @@
     PM_SOURCE_TMPF_PETRO_KEEP        = 0x0100,
     PM_SOURCE_TMPF_PETRO_SKIP        = 0x0200,
+    PM_SOURCE_TMPF_EXT_FIT           = 0x0400,  // not just galaxies (trails as well)
+    PM_SOURCE_TMPF_PETRO             = 0x0800,
 } pmSourceTmpF;
 
@@ -117,4 +119,5 @@
     pmSourceExtendedPars *extpars;      ///< extended source parameters
     pmSourceDiffStats *diffStats;       ///< extra parameters for difference detections
+    pmSourceGalaxyFits *galaxyFits;     ///< fits to galaxy models (psphotFullForce only)
     psArray *radialAper;		///< radial flux in circular apertures
     pmSource *parent;			///< reference to the master source from which this is derived
Index: trunk/psModules/src/objects/pmSourceExtendedPars.c
===================================================================
--- trunk/psModules/src/objects/pmSourceExtendedPars.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceExtendedPars.c	(revision 36375)
@@ -286,2 +286,25 @@
     return pars;
 }
+
+// *** pmSourceExtFitPars describes extra metadata related to an extended fit
+static void pmSourceGalaxyFitsFree (pmSourceGalaxyFits *tmp) {
+  
+    psFree (tmp->Flux);
+    psFree (tmp->dFlux);
+    psFree (tmp->chisq);
+
+    return;
+}
+
+pmSourceGalaxyFits *pmSourceGalaxyFitsAlloc (void) {
+
+    pmSourceGalaxyFits *tmp = (pmSourceGalaxyFits *) psAlloc(sizeof(pmSourceGalaxyFits));
+    psMemSetDeallocator(tmp, (psFreeFunc) pmSourceGalaxyFitsFree);
+
+    tmp->Flux  = psVectorAllocEmpty (25, PS_TYPE_F32);
+    tmp->dFlux = psVectorAllocEmpty (25, PS_TYPE_F32);
+    tmp->chisq = psVectorAllocEmpty (25, PS_TYPE_F32);
+    tmp->nPix = 0;
+
+    return tmp;
+}
Index: trunk/psModules/src/objects/pmSourceExtendedPars.h
===================================================================
--- trunk/psModules/src/objects/pmSourceExtendedPars.h	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceExtendedPars.h	(revision 36375)
@@ -82,4 +82,11 @@
 } pmSourceExtFitPars;
 
+typedef struct {
+  psVector *Flux;
+  psVector *dFlux;
+  psVector *chisq;
+  int nPix;
+} pmSourceGalaxyFits;
+
 pmSourceRadialFlux *pmSourceRadialFluxAlloc();
 bool psMemCheckSourceRadialFlux(psPtr ptr);
@@ -109,4 +116,7 @@
 pmSourceExtFitPars *pmSourceExtFitParsAlloc (void);
 
+pmSourceGalaxyFits *pmSourceGalaxyFitsAlloc (void);
+
+
 /// @}
 # endif /* PM_SOURCE_H */
Index: trunk/psModules/src/objects/pmSourceFitModel.c
===================================================================
--- trunk/psModules/src/objects/pmSourceFitModel.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceFitModel.c	(revision 36375)
@@ -59,4 +59,5 @@
     opt->maxTol = 1.00;
     opt->weight = 1.00;
+    opt->nsigma = 5.00;
     opt->maxChisqDOF = NAN;
     opt->poissonErrors = true;
@@ -281,5 +282,11 @@
     // set the model success or failure status
     model->flags |= PM_MODEL_STATUS_FITTED;
-    if (!fitStatus) model->flags |= PM_MODEL_STATUS_NONCONVERGE;
+    if (!fitStatus) {
+	if (isnan(myMin->value)) {
+	  model->flags |= PM_MODEL_STATUS_NAN_CHISQ;
+	} else {
+	  model->flags |= PM_MODEL_STATUS_NONCONVERGE;
+	}
+    }
 
     if (myMin->chisqConvergence) {
Index: trunk/psModules/src/objects/pmSourceFitModel.h
===================================================================
--- trunk/psModules/src/objects/pmSourceFitModel.h	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceFitModel.h	(revision 36375)
@@ -34,4 +34,5 @@
     float weight;			///< use this weight for constant-weight fits
     float covarFactor;			///< covariance factor for calculating the chisq
+    float nsigma;                       ///< how far out to convolve
     bool poissonErrors;			///< use poisson errors for fits?
     bool saveCovariance;
Index: trunk/psModules/src/objects/pmSourceFitPCM.c
===================================================================
--- trunk/psModules/src/objects/pmSourceFitPCM.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceFitPCM.c	(revision 36375)
@@ -51,4 +51,6 @@
 # define TIMING 0
 
+bool pmSourceChisqModelFlux (pmSource *source, pmModel *model, psImageMaskType maskVal);
+
 bool pmSourceFitPCM (pmPCMdata *pcm, pmSource *source, pmSourceFitOptions *fitOptions, psImageMaskType maskVal, psImageMaskType markVal, int psfSize) {
     
@@ -113,5 +115,6 @@
     } else {
 	// xxx this is wrong because it does not convolve with the psf
-	pmSourceChisqUnsubtracted (source, pcm->modelConv, maskVal);
+	pmPCMMakeModel (source, pcm->modelConv, pcm->nsigma, maskVal, psfSize);
+	pmSourceChisqModelFlux (source, pcm->modelConv, maskVal);
     }
     if (TIMING) { t4 = psTimerMark ("pmSourceFitPCM"); }
@@ -119,5 +122,12 @@
     // set the model success or failure status
     pcm->modelConv->flags |= PM_MODEL_STATUS_FITTED;
-    if (!fitStatus) pcm->modelConv->flags |= PM_MODEL_STATUS_NONCONVERGE;
+
+    if (!fitStatus) {
+	if (isnan(myMin->value)) {
+	    pcm->modelConv->flags |= PM_MODEL_STATUS_NAN_CHISQ;
+	} else {
+	    pcm->modelConv->flags |= PM_MODEL_STATUS_NONCONVERGE;
+	}
+    }
 
     if (myMin->chisqConvergence) {
Index: trunk/psModules/src/objects/pmSourceFitSet.c
===================================================================
--- trunk/psModules/src/objects/pmSourceFitSet.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceFitSet.c	(revision 36375)
@@ -352,5 +352,11 @@
         // set the model success or failure status
         model->flags |= PM_MODEL_STATUS_FITTED;
-        if (!fitStatus) model->flags |= PM_MODEL_STATUS_NONCONVERGE;
+	if (!fitStatus) {
+	  if (isnan(myMin->value)) {
+	    model->flags |= PM_MODEL_STATUS_NAN_CHISQ;
+	  } else {
+	    model->flags |= PM_MODEL_STATUS_NONCONVERGE;
+	  }
+	}
 
         // models can go insane: reject these
Index: trunk/psModules/src/objects/pmSourceIO.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceIO.c	(revision 36375)
@@ -69,4 +69,5 @@
 			psString *xfitname,    // Extension name for extended fitted measurements
 			psString *xradname,    // Extension name for radial apertures
+			psString *xgalname,    // Extension name for galaxy shapes
 			const pmFPAfile *file, // File of interest
 			const pmFPAview *view  // View to level of interest
@@ -140,4 +141,14 @@
         }
         *xradname = pmFPAfileNameFromRule (rule, file, view);
+    }
+
+    // EXTNAME for radial apertures
+    if (xgalname) {
+        const char *rule = psMetadataLookupStr(&status, menu, "CMF.XGAL");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.XGAL in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        *xgalname = pmFPAfileNameFromRule (rule, file, view);
     }
 
@@ -362,4 +373,7 @@
 	    status &= pmSourcesWrite_##TYPE##_XRAD (file->fits, readout, sources, file->header, xradname, recipe); \
 	}								\
+	if (xgalname) {							\
+	    status &= pmSourcesWrite_##TYPE##_XGAL (file->fits, sources, xgalname, recipe); \
+	}								\
     }
 
@@ -464,8 +478,11 @@
         }
 
-        // if this is not TRUE, the output files only contain the psf measurements.
-        bool XSRC_OUTPUT = psMetadataLookupBool(&status, recipe, "EXTENDED_SOURCE_ANALYSIS");
+        // if none of these are TRUE, the output files only contain the psf measurements.
+	bool doPetrosian = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+	bool doAnnuli    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+        bool XSRC_OUTPUT = doPetrosian || doAnnuli;
         bool XFIT_OUTPUT = psMetadataLookupBool(&status, recipe, "EXTENDED_SOURCE_FITS");
         bool XRAD_OUTPUT = psMetadataLookupBool(&status, recipe, "RADIAL_APERTURES");
+        bool XGAL_OUTPUT = psMetadataLookupBool(&status, recipe, "GALAXY_SHAPES");
 
         // define the EXTNAME values for the different data segments:
@@ -476,8 +493,10 @@
         psString xfitname = NULL;
         psString xradname = NULL;
+        psString xgalname = NULL;
         if (!pmSourceIOextnames(&headname, &dataname, &deteffname, 
 				XSRC_OUTPUT ? &xsrcname : NULL,
 				XFIT_OUTPUT ? &xfitname : NULL, 
 				XRAD_OUTPUT ? &xradname : NULL, 
+				XGAL_OUTPUT ? &xgalname : NULL, 
 				file, view)) {
             return false;
@@ -563,4 +582,7 @@
 		psMetadataAddStr (outhead, PS_LIST_TAIL, "XRADNAME", PS_META_REPLACE, "name of XRAD table extension", xradname);
             }
+            if (xgalname) {
+		psMetadataAddStr (outhead, PS_LIST_TAIL, "XGALNAME", PS_META_REPLACE, "name of XGAL table extension", xgalname);
+            }
 
             // these are case-sensitive since the EXTYPE is case-sensitive
@@ -609,4 +631,5 @@
 	psFree (xfitname);
 	psFree (xradname);
+	psFree (xgalname);
 	psFree (deteffname);
 
@@ -620,7 +643,28 @@
 	psFree (xfitname);
 	psFree (xradname);
+	psFree (xgalname);
 	psFree (deteffname);
 	return false;
 
+      case PM_FPA_FILE_CFF: {
+        // determine the output table format
+        psMetadata *recipe = psMetadataLookupMetadata(&status, config->recipes, "PSPHOT");
+        if (!status) {
+	    psError(PS_ERR_UNKNOWN, true, "missing recipe PSPHOT in config data");
+	    return false;
+        }
+
+        hdu = pmFPAviewThisHDU (view, fpa);
+        pmConfigConformHeader(hdu->header, file->format);
+        psFitsWriteBlank (file->fits, hdu->header, NULL);
+        file->header = hdu->header;
+        file->wrote_phu = true;
+	if (!pmSourcesWrite_CFF(readout, file->fits, sources, hdu->header, recipe)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to write CFF");
+            return false;
+        }
+        break;
+      }
+        
       default:
         fprintf (stderr, "warning: type mismatch\n");
@@ -914,4 +958,16 @@
     psArray *sources = NULL;
     pmHDU *hdu;
+
+    // define the EXTNAME values for the different data segments:
+    psString headname = NULL;
+    psString dataname = NULL;
+    psString deteffname = NULL;
+    psString xsrcname = NULL;
+    psString xfitname = NULL;
+    psString xradname = NULL;
+    psString xgalname = NULL;
+
+    psMetadata *tableHeader = NULL;
+    char *xtension = NULL;
 
     switch (file->type) {
@@ -963,12 +1019,4 @@
         hdu = pmFPAviewThisHDU (view, file->fpa);
 
-        // define the EXTNAME values for the different data segments:
-        psString headname = NULL;
-        psString dataname = NULL;
-        psString deteffname = NULL;
-        psString xsrcname = NULL;
-        psString xfitname = NULL;
-        psString xradname = NULL;
-
         // determine the output table format. Assume if we need to output extendend source
         // parameters that they may exist in the input. 
@@ -981,8 +1029,12 @@
         }
 
-        // if this is not TRUE, the output files only contain the psf measurements.
-        bool XSRC_OUTPUT = psMetadataLookupBool(&status, recipe, "EXTENDED_SOURCE_ANALYSIS");
+        // if none of these are TRUE, we only read the psf measurements
+        // XXX: shouldn't we look for these extensions and read the regardless of the recipe values?
+	bool doPetrosian = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+	bool doAnnuli    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+        bool XSRC_OUTPUT = doPetrosian || doAnnuli;
         bool XFIT_OUTPUT = psMetadataLookupBool(&status, recipe, "EXTENDED_SOURCE_FITS");
         bool XRAD_OUTPUT = psMetadataLookupBool(&status, recipe, "RADIAL_APERTURES");
+        bool XGAL_OUTPUT = false; // psMetadataLookupBool(&status, recipe, "GALAXY_SHAPES");
 
         if (!pmSourceIOextnames(&headname, &dataname, &deteffname, 
@@ -990,4 +1042,5 @@
                 XFIT_OUTPUT ? &xfitname : NULL, 
                 XRAD_OUTPUT ? &xradname : NULL,
+                XGAL_OUTPUT ? &xgalname : NULL,
                 file, view)) {
             return false;
@@ -1033,8 +1086,8 @@
         }
 
-        psMetadata *tableHeader = psFitsReadHeader(NULL, file->fits); // The FITS header
+        tableHeader = psFitsReadHeader(NULL, file->fits); // The FITS header
         if (!tableHeader) psAbort("cannot read table header");
 
-        char *xtension = psMetadataLookupStr (NULL, tableHeader, "XTENSION");
+        xtension = psMetadataLookupStr (NULL, tableHeader, "XTENSION");
         if (!xtension) psAbort("cannot read table type");
 	if (strcmp (xtension, "BINTABLE")) {
@@ -1132,4 +1185,54 @@
         break;
 
+      case PM_FPA_FILE_CFF:
+        // read in header, if not yet loaded
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+
+	// look these up in the camera config?
+	// headrule = {CHIP.NAME}.hdr
+	// datarule = {CHIP.NAME}.cff
+
+        // define the EXTNAME values for the different data segments:
+        headname = pmFPAfileNameFromRule("{CHIP.NAME}.hdr", file, view);
+        dataname = pmFPAfileNameFromRule("{CHIP.NAME}.cff", file, view);
+
+        // advance to the IMAGE HEADER extension
+        if (hdu->header == NULL) {
+            // if the IMAGE header does not exist, we have no data for this view
+            if (!psFitsMoveExtNameClean (file->fits, headname)) {
+                readout->data_exists = false;
+                psFree (headname);
+                psFree (dataname);
+                return true;
+            }
+            hdu->header = psFitsReadHeader (NULL, file->fits);
+        }
+
+        // advance to the table data extension
+        // since we have read the IMAGE header, the TABLE header should exist
+        if (!psFitsMoveExtName (file->fits, dataname)) {
+            psAbort("cannot find data extension %s in %s", dataname, file->filename);
+        }
+
+        tableHeader = psFitsReadHeader(NULL, file->fits); // The FITS header
+        if (!tableHeader) psAbort("cannot read table header");
+
+	// verify this is a binary table
+        char *xtension = psMetadataLookupStr (NULL, tableHeader, "XTENSION");
+        if (!xtension) psAbort("cannot read table type");
+	if (strcmp (xtension, "BINTABLE")) {
+	    psWarning ("no binary table in extension %s, skipping\n", dataname);
+            psFree(tableHeader);
+	    return false;
+	}
+
+	sources = pmSourcesRead_CFF(file->fits, hdu->header);
+
+        psTrace("psModules.objects", 6, "read CMF table from %s : %s : %s", file->filename, headname, dataname);
+        psFree (headname);
+        psFree (dataname);
+        psFree (tableHeader);
+        break;
+
       default:
         fprintf (stderr, "warning: type mismatch\n");
Index: trunk/psModules/src/objects/pmSourceIO.h
===================================================================
--- trunk/psModules/src/objects/pmSourceIO.h	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceIO.h	(revision 36375)
@@ -21,4 +21,5 @@
   bool pmSourcesWrite_##TYPE##_XFIT(psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname); \
   bool pmSourcesWrite_##TYPE##_XRAD(psFits *fits, pmReadout *readout, psArray *sources, psMetadata *imageHeader, char *extname, psMetadata *recipe); \
+  bool pmSourcesWrite_##TYPE##_XGAL(psFits *fits, psArray *sources, char *extname, psMetadata *recipe); \
   psArray *pmSourcesRead_##TYPE (psFits *fits, psMetadata *header); \
   bool pmSourcesRead_##TYPE##_XSRC (psFits *fits, pmReadout *readout, psMetadata *header, psMetadata *tableHeader, psArray *sources, long *index); \
@@ -52,4 +53,6 @@
 
 psArray *pmSourcesReadCMP (char *filename, psMetadata *header);
+psArray *pmSourcesRead_CFF (psFits *fits, psMetadata *header);
+bool pmSourcesWrite_CFF (pmReadout *readout, psFits *fits, psArray *sources, psMetadata *header, psMetadata *recipe);
 
 bool pmSourcesWritePSFs (psArray *sources, char *filename);
Index: trunk/psModules/src/objects/pmSourceIO_CFF.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_CFF.c	(revision 36375)
+++ trunk/psModules/src/objects/pmSourceIO_CFF.c	(revision 36375)
@@ -0,0 +1,343 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-18 02:44:19 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmTrend2D.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmSpan.h"
+#include "pmFootprintSpans.h"
+#include "pmFootprint.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModelFuncs.h"
+#include "pmModel.h"
+#include "pmModelUtils.h"
+#include "pmModelClass.h"
+#include "pmSourceMasks.h"
+#include "pmSourceExtendedPars.h"
+#include "pmSourceDiffStats.h"
+#include "pmSourceSatstar.h"
+#include "pmSource.h"
+#include "pmSourceFitModel.h"
+#include "pmPSF.h"
+#include "pmPSFtry.h"
+
+#include "pmSourceIO.h"
+#include "pmSourceOutputs.h"
+
+// read in a readout from the fits file
+psArray *pmSourcesRead_CFF (psFits *fits, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    bool status;
+    psEllipseAxes axes;
+
+    // define PSF model type
+    // XXX need to carry the extra model parameters
+    int modelType = pmModelClassGetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSFMODEL");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelClassGetType (PSF_NAME);
+    }
+    PS_ASSERT_INT_NONNEGATIVE(modelType, NULL);
+
+    // We get the size of the table, and allocate the array of sources first because the table
+    // is large and ephemeral --- when the table gets blown away, whatever is allocated after
+    // the table is read blocks the free.  In fact, it's better to read the table row by row.
+    long numSources = psFitsTableSize(fits); // Number of sources in table
+    psArray *sources = psArrayAlloc(numSources); // Array of sources, to return
+
+    // convert the table to the pmSource entriesa
+    for (int i = 0; i < numSources; i++) {
+        psMetadata *row = psFitsReadTableRow(fits, i); // Table row
+        if (!row) {
+            psError(psErrorCodeLast(), false, "Unable to read row %d of sources", i);
+            psFree(sources);
+            return NULL;
+        }
+
+	// here are the things we read from the FITS table (XXX modify names if needed)
+
+	unsigned int ID  = psMetadataLookupU32 (&status, row, "ID");
+	float X          = psMetadataLookupF32 (&status, row, "X");
+        assert(status);  // poorly constructed cff
+	float Y          = psMetadataLookupF32 (&status, row, "Y");
+        assert(status);  // poorly constructed cff
+        float flux       = psMetadataLookupF32 (&status, row, "FLUX"); // guess at the instrumental flux
+        if (!isfinite(X) || !isfinite(Y) || !isfinite(flux)) {
+            psError(PS_ERR_IO, true, "source ID %d is invalid x: %f y: %f flux: %f", ID, X, Y, flux);
+            psFree(sources);
+            return NULL;
+        }
+
+
+        float apRadius   = psMetadataLookupF32 (&status, row, "AP_RADIUS");
+        float kronRadius = psMetadataLookupF32 (&status, row, "KRON_RADIUS");
+        float petRadius  = psMetadataLookupF32 (&status, row, "PETRO_RADIUS");
+        bool fitGalaxy   = psMetadataLookupU8 (&status, row, "FIT_GALAXY");
+        bool psfStar     = psMetadataLookupU8 (&status, row, "PSF_STAR");
+
+        float Rmajor     = psMetadataLookupF32 (&status, row, "R_MAJOR");
+        float Rminor     = psMetadataLookupF32 (&status, row, "R_MINOR");
+        float theta      = psMetadataLookupF32 (&status, row, "THETA");
+
+        // XXX: we need to put a lookup table in the cff header to define the correspondence of the
+        // model type values in the cff with our models. (We want to use an interger for efficiency
+        // but the value for each model type is set on the organization of the the array in pmModelClass.c
+        // For now use the input values verbatim and trust the user that this is valid value
+        int   galaxyModelType = psMetadataLookupS32(&status, row, "MODEL_TYPE");
+        float Sindex     = psMetadataLookupF32 (&status, row, "INDEX"); // Should this be PAR_07 not sersic index
+
+        pmSource *source = pmSourceAlloc ();
+        pmModel *model = pmModelAlloc (modelType);
+        source->modelPSF  = model;
+        source->type = PM_SOURCE_TYPE_STAR; // XXX this should be added to the flags
+
+	// XXX we can set this in general, but for a specific image, we need to weed out SATSTARS
+        if (psfStar) {
+	    source->tmpFlags |= PM_SOURCE_TMPF_CANDIDATE_PSFSTAR;
+	}
+
+	// NOTE: A SEGV here because "model" is NULL is probably caused by not initialising the models.
+	psF32 *PAR = model->params->data.F32;
+	psF32 *dPAR = model->dparams->data.F32;
+
+        source->seq       = ID;
+        PAR[PM_PAR_XPOS]  = X;
+        PAR[PM_PAR_YPOS]  = Y;
+
+	dPAR[PM_PAR_XPOS] = 0.0;
+	dPAR[PM_PAR_YPOS] = 0.0;
+
+	PAR[PM_PAR_SKY]   = 0.0;
+	dPAR[PM_PAR_SKY]  = 0.0;
+
+	PAR[PM_PAR_I0]    = 1.0;
+	dPAR[PM_PAR_I0]   = 0.0;
+
+	source->sky       = PAR[PM_PAR_SKY];
+	source->skyErr    = dPAR[PM_PAR_SKY];
+
+	source->psfMag    = 0.0;
+	source->psfMagErr = 0.0;
+	source->apMag     = 0.0;
+        source->apRadius  = apRadius;
+
+	// we generate a somewhat fake PSF model here -- 
+	// in most (all?) contexts, we will replace this with a measured psf model
+	// elsewhere
+	axes.major        = 1.0;
+	axes.minor        = 1.0;
+	axes.theta        = 0.0;
+	pmPSF_AxesToModel (PAR, axes, modelType);
+
+	// peak->detValue, rawFlux, smoothFlux are all set to the flux argument which is counts per second
+        source->peak      = pmPeakAlloc(X, Y, flux, PM_PEAK_LONE);
+        source->peak->xf  = X; // pmPeakAlloc converts X,Y to int, so reset here
+        source->peak->yf  = Y; // pmPeakAlloc converts X,Y to int, so reset here
+        source->peak->dx  = 0.0;
+        source->peak->dy  = 0.0;
+
+        source->moments = pmMomentsAlloc ();
+	source->moments->Mx = X;
+	source->moments->My = Y;
+	source->moments->Mrf = kronRadius * 0.4; // kronRadius is 2.5 * first radial moment
+	source->tmpFlags |= PM_SOURCE_TMPF_MOMENTS_MEASURED;
+	// XXX does the above cause a problem with pmSourceMoments (do we run it? yes we do)
+
+	if (isfinite(petRadius)) {
+	    source->extpars = pmSourceExtendedParsAlloc ();
+	    source->extpars->petrosianRadius = petRadius;
+	}
+
+        // XXX: should use < 0 as invalid galaxyModelType
+
+        if (fitGalaxy && galaxyModelType > 0) {
+            source->modelFits = psArrayAllocEmpty (1);
+	    pmModel *model = pmModelAlloc(galaxyModelType);
+	    psF32 *xPAR = model->params->data.F32;
+
+	    xPAR[PM_PAR_SKY]  = 0.0;
+	    xPAR[PM_PAR_I0]   = 1.0;
+	    xPAR[PM_PAR_XPOS] = X;
+	    xPAR[PM_PAR_YPOS] = Y;
+	    
+	    psEllipseAxes galaxyAxes;
+	    galaxyAxes.major = Rmajor;
+	    galaxyAxes.minor = Rminor;
+	    galaxyAxes.theta = theta * PS_RAD_DEG;
+
+	    pmPSF_AxesToModel (xPAR, galaxyAxes, galaxyModelType);
+	    if (model->params->n > 7) {
+                xPAR[PM_PAR_7] = 0.5 / Sindex;
+	    }
+
+	    psArrayAdd (source->modelFits, 1, model);
+
+#ifdef notyet
+            // XXX: set source->modelEXT to this model and flag as extended so that we have a better
+            // shot at subtracting extended sources?
+
+            // This doesn't work right. Need to do some more work to flesh out the model before it can be
+            // used in subtaction. Better idea might be to do 2 passes in psphotFullForceReadout to
+            // First find the best extended models and then do everything else.
+            source->modelEXT = psMemIncrRefCounter(model);
+            // is this safe? (no see above)
+            source->type = PM_SOURCE_TYPE_EXTENDED;
+            source->mode |= PM_SOURCE_MODE_EXTMODEL; 
+#endif
+
+	    psFree (model);
+        }
+
+        sources->data[i] = source;
+        psFree(row);
+    }
+
+    return sources;
+}
+
+bool pmSourcesWrite_CFF(pmReadout *readout, psFits *fits, psArray *sources, psMetadata *header, psMetadata *recipe) {
+    
+    char *extname = "SkyChip.cff";
+    
+    bool mdok;
+    psF32 exptime = psMetadataLookupF32(&mdok, readout->parent->concepts, "CELL.EXPOSURE");
+    PS_ASSERT(mdok, false);
+
+    psArray *table = psArrayAllocEmpty(sources->n);
+
+    pmModelType sersicModelType = pmModelClassGetType("PS_MODEL_SERSIC");
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *thisSource = sources->data[i];
+        pmSource *source = thisSource->parent ? thisSource->parent : thisSource;
+
+        psF32 xPos, yPos, flux, rMajor, rMinor, theta;
+        psS32 modelType = 0;
+        bool fitGalaxy = false;
+        bool psfStar = false;
+        psF32 sersicIndex = 0;
+        if (source->modelFits == NULL) {
+            pmModel *model = source->modelPSF;
+            if (model == NULL) continue;
+            psF32 *PAR = model->params->data.F32;
+            if (!isfinite(PAR[PM_PAR_SXX]) || !isfinite(PAR[PM_PAR_SYY])  || !isfinite(PAR[PM_PAR_SXY]) ||
+                !isfinite(source->psfFlux)) {
+                continue;
+            }
+
+            xPos = model->params->data.F32[PM_PAR_XPOS];
+            yPos = model->params->data.F32[PM_PAR_YPOS];
+            flux = source->psfFlux;
+            psfStar = (source->mode & PM_SOURCE_MODE_PSFSTAR) ? true : false;
+            rMajor = 0;
+            rMinor = 0;
+            theta = 0;
+        } else {
+            // Choose the extended model to use for this source.
+            // For now we use the one set as the modelEXT for this source which had the best fit.
+            // XXX: make this controllable by recipe
+            //   use best (as implemented here)
+            //   choose specific type
+            int iModel = -1;
+            if (source->modelEXT) {
+                pmModelType ext_model_type = source->modelEXT->type;
+                for (int j=0; j<source->modelFits->n; j++) {
+                    pmModel *aModel = source->modelFits->data[j];
+                    if (aModel->type == ext_model_type) {
+                        iModel = j;
+                        break;
+                    }
+                }
+            }
+            if (iModel == -1) {
+                // Can this happen? perhaps if model type is qgauss (extended but below s/n for model fits?
+                // XXX: Should this be an assert?
+                continue;
+            }
+            pmModel *model = source->modelFits->data[iModel];
+            psF32 *PAR = model->params->data.F32;
+            xPos = PAR[PM_PAR_XPOS];
+            yPos = PAR[PM_PAR_YPOS];
+            if (model->type == pmModelClassGetType("PS_MODEL_TRAIL")) {
+                // XXX: do we need to handle this type
+                continue;
+	    } else {
+		if (!isfinite(PAR[PM_PAR_SXX]) || !isfinite(PAR[PM_PAR_SYY])  || !isfinite(PAR[PM_PAR_SXY]) ||
+                    !isfinite(model->mag)) { 
+                    // bad model
+                    continue;
+		}
+                psEllipseAxes axes = pmPSF_ModelToAxes (PAR, model->type);
+                rMajor = axes.major;
+                rMinor = axes.minor;
+                theta  = axes.theta*PS_DEG_RAD;
+                flux = pow(10.0, -0.4*model->mag);
+                fitGalaxy = true;
+            }
+            modelType = model->type;
+            if (modelType == sersicModelType) {
+                PS_ASSERT_FLOAT_LARGER_THAN(PAR[PM_PAR_7], 0.0, false);
+                sersicIndex = 0.5 / PAR[PM_PAR_7];
+            }
+        }
+        psMetadata *row = psMetadataAlloc();
+        psMetadataAddU32 (row, PS_LIST_TAIL, "ID",         0,   "IPP detection identifier",  source->seq);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "X",                0, "x coordinate",          xPos);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "Y",                0, "y coordinate",          yPos);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "FLUX",             0, "flux per second",       flux/exptime);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "AP_RADIUS",        0, "aperture radius",       source->apRadius);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "KRON_RADIUS",      0, "Kron radius",           source->moments->Mrf * 2.5);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "PETRO_RADIUS",     0, "Petrosian Radius",      source->extpars ? source->extpars->petrosianRadius : NAN);
+        psMetadataAddBool (row, PS_LIST_TAIL, "FIT_GALAXY",      0, "source has xfit",       fitGalaxy); 
+        psMetadataAddBool (row, PS_LIST_TAIL, "PSF_STAR",        0, "source was psf star",   psfStar);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "R_MAJOR",          0, "radius of major axis",  rMajor);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "R_MINOR",          0, "radius of minor axis",  rMinor);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "THETA",            0, "theta",                 theta);
+        psMetadataAddS32 (row, PS_LIST_TAIL, "MODEL_TYPE",       0, "model type",            modelType);
+        psMetadataAddF32 (row, PS_LIST_TAIL, "INDEX",            0, "sersic index",          sersicIndex);
+
+        psArrayAdd(table, 100, row);
+    }
+
+    if (!psFitsWriteTable(fits, NULL, table, extname)) {
+        psError(psErrorCodeLast(), false, "writing ext data %s\n", extname);
+        psFree(table);
+        psFree(header);
+        return false;
+    }
+    psFree(table);
+    // psFree(header);
+
+    return true;
+}
Index: trunk/psModules/src/objects/pmSourceIO_CMF.c.in
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_CMF.c.in	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceIO_CMF.c.in	(revision 36375)
@@ -789,4 +789,6 @@
     char name[64];
 
+    pmModelType modelTypeTrail = pmModelClassGetType("PS_MODEL_TRAIL");
+
     // create a header to hold the output data
     psMetadata *outhead = psMetadataAlloc ();
@@ -798,9 +800,9 @@
     sources = psArraySort (sources, pmSourceSortByFlux);
 
-    @>PS1_DV2@ float magOffset; 
-    @>PS1_DV2@ float zeroptErr; 
-    @>PS1_DV2@ float fwhmMajor; 
-    @>PS1_DV2@ float fwhmMinor;
-    @>PS1_DV2@ pmSourceOutputsCommonValues (&magOffset, &zeroptErr, &fwhmMajor, &fwhmMinor, readout, imageHeader);
+    float magOffset; 
+    float zeroptErr; 
+    float fwhmMajor; 
+    float fwhmMinor;
+    pmSourceOutputsCommonValues (&magOffset, &zeroptErr, &fwhmMajor, &fwhmMinor, readout, imageHeader);
 
     // we are writing one row per model; we need to write out same number of columns for each row: find the max Nparams
@@ -823,4 +825,12 @@
     @>PS1_DV2@ pmChip *chip = readout->parent->parent;
 
+    pmModelStatus badModel = PM_MODEL_STATUS_NONE;
+    badModel |= PM_MODEL_STATUS_BADARGS;
+    badModel |= PM_MODEL_STATUS_OFFIMAGE;
+    badModel |= PM_MODEL_STATUS_NAN_CHISQ;
+    badModel |= PM_MODEL_SERSIC_PCM_FAIL_GUESS;
+    badModel |= PM_MODEL_SERSIC_PCM_FAIL_GRID;
+    badModel |= PM_MODEL_PCM_FAIL_GUESS;
+
     table = psArrayAllocEmpty (sources->n);
 
@@ -847,5 +857,6 @@
 
 	    // skip models which were not actually fitted
-	    if (model->flags & PM_MODEL_STATUS_BADARGS) continue;
+	    // XXX 
+	    if (model->flags & badModel) continue;
 
             PAR = model->params->data.F32;
@@ -853,6 +864,21 @@
             xPos = PAR[PM_PAR_XPOS];
             yPos = PAR[PM_PAR_YPOS];
-            xErr = dPAR[PM_PAR_XPOS];
-            yErr = dPAR[PM_PAR_YPOS];
+
+	    // for the extended source models, we do not always fit the centroid in the non-linear fitting process
+	    // current situation (hard-wired into psphotSourceFits.c:psphotFitPCM,
+	    // SERSIC, DEV, EXP : X,Y not fitted (PCM and not PCM)
+	    // TRAIL : X,Y are fitted
+	    // 
+	    
+	    // XXX this should be based on what happened, not on the model type
+	    if (model->type == modelTypeTrail) {
+		xErr = dPAR[PM_PAR_XPOS];
+		yErr = dPAR[PM_PAR_YPOS];
+	    } else {
+		// this is definitely an underestimate since it does not
+		// account for the extent of the source
+		xErr = fwhmMajor * model->magErr / 2.35;
+		yErr = fwhmMinor * model->magErr / 2.35;
+	    }
 
 	    @>PS1_DV2@ psSphere ptSky = {0.0, 0.0, 0.0, 0.0};
@@ -870,5 +896,4 @@
             row = psMetadataAlloc ();
 
-            // XXX we are not writing out the mode (flags) or the type (psf, ext, etc)
 	    // the psMetadataAdd entry and the double quotes are used by grep to select the output fields for automatic documentation
 	    // This set of psMetadataAdd Entries marks the "----" "Start of the XFIT segment"
@@ -888,6 +913,10 @@
 	    @>PS1_DV2@ float calMag = isfinite(magOffset) ? model->mag + magOffset : NAN;
 	    @>PS1_DV2@ psMetadataAdd (row, PS_LIST_TAIL, "EXT_CAL_MAG", PS_DATA_F32, "EXT Magnitude using supplied calibration",   calMag);
-	    @>PS1_DV2@ psMetadataAdd (row, PS_LIST_TAIL, "EXT_CHISQ",   PS_DATA_F32, "EXT Magnitude using supplied calibration",   model->chisq);
-	    @>PS1_DV2@ psMetadataAdd (row, PS_LIST_TAIL, "EXT_NDOF",    PS_DATA_S32, "EXT Magnitude using supplied calibration",   model->nDOF);
+	    @>PS1_DV2,PS1_SV?@ psMetadataAdd (row, PS_LIST_TAIL, "EXT_CHISQ",   PS_DATA_F32, "EXT Model Chisq",   model->chisq);
+	    @>PS1_DV2,PS1_SV?@ psMetadataAdd (row, PS_LIST_TAIL, "EXT_NDOF",    PS_DATA_S32, "EXT Model num degrees of freedom",   model->nDOF);
+	    @>PS1_SV1,PS1_SV?@ psMetadataAdd (row, PS_LIST_TAIL, "EXT_MODEL_TYPE",    PS_DATA_S32, "type for chosen EXT_MODEL",   source->modelEXT ? source->modelEXT->type : -1);
+
+	    // EAM : adding for PV2 outputs:
+	    @>PS1_SV1@ psMetadataAdd (row, PS_LIST_TAIL, "EXT_FLAGS", PS_DATA_S16, "model fit flags (pmModelStatus)", source->modelEXT ? source->modelEXT->flags : 0);
 
             @>PS1_DV2@ psMetadataAddF32 (row, PS_LIST_TAIL, "POSANGLE",   0, "position angle at source (degrees)",         posAngle);
@@ -946,5 +975,5 @@
 
                 snprintf (name, 64, "EXT_PAR_%02d", k);
-
+	       
                 if (k < model->params->n) {
                     psMetadataAddF32 (row, PS_LIST_TAIL, name, 0, "", model->params->data.F32[k]);
@@ -956,5 +985,5 @@
 	    // optionally, write out the covariance matrix values
 	    // XXX do I need to pad this to match the biggest covar matrix?
-	    if (model->covar) {
+	    if (false && model->covar) {
 		for (int iy = 0; iy < model->covar->numCols; iy++) {
 		    for (int ix = iy; ix < model->covar->numCols; ix++) {
@@ -1058,4 +1087,17 @@
         model->magErr = psMetadataLookupF32(&status, row, "EXT_INST_MAG_SIG");
 
+        model->chisq = psMetadataLookupF32(&status, row, "EXT_CHISQ");
+        model->nDOF = psMetadataLookupF32(&status, row, "EXT_NDOF");
+
+        // EXT_MODEL_TYPE gives the model chosen by psphot as the best.
+        // Putting this into the XFIT table makes 3 copies of it (one for each model)
+        // but since we have many fewer XFIT rows than psf rows that is cheaper than putting it
+        // in the psf table.
+        psS32 extModelType = psMetadataLookupS32(&status, row, "EXT_MODEL_TYPE");
+        if (!status) {
+            // older cmfs don't have this column
+            extModelType = -1;
+        }
+
         psEllipseAxes axes;
         axes.major = psMetadataLookupF32(&status, row, "EXT_WIDTH_MAJ");
@@ -1072,20 +1114,40 @@
         if (model->params->n > 7) {
             PAR[7] = psMetadataLookupF32(&status, row, "EXT_PAR_07");
-        }
-        // read the covariance matrix
-        int nparams = model->params->n;
-        psImage *covar = psImageAlloc(nparams, nparams, PS_TYPE_F32);
-        for (int y = 0; y < nparams; y++) {
-            for (int x = 0; x < nparams; x++) {
-                char name[64];
-                snprintf(name, 64, "EXT_COVAR_%02d_%02d", y, x);
-                covar->data.F32[y][x] = psMetadataLookupF32(&status, row, name);
+            // XXX add an error:
+	    // dPAR[7] = psMetadataLookupF32(&status, row, "EXT_PAR_07_");
+        }
+
+	// XXX : make this depend on what is in the cmf
+	if (0) {
+	    // read the covariance matrix
+	    int nparams = model->params->n;
+	    psImage *covar = psImageAlloc(nparams, nparams, PS_TYPE_F32);
+	    for (int y = 0; y < nparams; y++) {
+		for (int x = 0; x < nparams; x++) {
+		    char name[64];
+		    snprintf(name, 64, "EXT_COVAR_%02d_%02d", y, x);
+		    covar->data.F32[y][x] = psMetadataLookupF32(&status, row, name);
+		}
+	    }
+	    model->covar = covar;
+	}
+
+        if (modelType == extModelType) {
+            // The software that created this source picked this model as the best of the fits. 
+            // Set the extModel to point to it.
+            // This is important for programs like psastro (skycal) so that its output cmfs
+            // will have valid EXT_MODEL_TYPE
+            psFree(source->modelEXT);
+            source->modelEXT = psMemIncrRefCounter(model);
+            source->type = PM_SOURCE_TYPE_EXTENDED;
+            if (0) {
+                // since FLAGS were read we don't need to do this
+                source->mode |= PM_SOURCE_MODE_EXTMODEL;
+                source->mode |= PM_SOURCE_MODE_NONLINEAR_FIT;
             }
         }
-        model->covar = covar;
 
         psArrayAdd(source->modelFits, 1, model);
         psFree(model);
-
         psFree(row);
     }
@@ -1209,8 +1271,8 @@
 
 	write_annuli:
-	    psMetadataAdd (row, PS_LIST_TAIL, "APER_FLUX",     	 PS_DATA_VECTOR, "flux within annuli",       radFlux);
-	    psMetadataAdd (row, PS_LIST_TAIL, "APER_FLUX_ERR", 	 PS_DATA_VECTOR, "flux error in annuli",     radFluxErr);
-	    psMetadataAdd (row, PS_LIST_TAIL, "APER_FLUX_STDEV", PS_DATA_VECTOR, "flux standard deviation",  radFluxStdev);
-	    psMetadataAdd (row, PS_LIST_TAIL, "APER_FILL",       PS_DATA_VECTOR, "fill factor of annuli",    radFill);
+	    psMetadataAddVector (row, PS_LIST_TAIL, "APER_FLUX",       PS_META_REPLACE, "flux within annuli",       radFlux);
+	    psMetadataAddVector (row, PS_LIST_TAIL, "APER_FLUX_ERR",   PS_META_REPLACE, "flux error in annuli",     radFluxErr);
+	    psMetadataAddVector (row, PS_LIST_TAIL, "APER_FLUX_STDEV", PS_META_REPLACE, "flux standard deviation",  radFluxStdev);
+	    psMetadataAddVector (row, PS_LIST_TAIL, "APER_FILL",       PS_META_REPLACE, "fill factor of annuli",    radFill);
 	    psFree (radFlux);
 	    psFree (radFluxErr);
@@ -1343,2 +1405,88 @@
     return true;
 }
+
+// XXX where should I record the number of columns??
+bool pmSourcesWrite_CMF_@CMFMODE@_XGAL (psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+{
+    bool status = false;
+
+    // perform full non-linear fits / extended source analysis?
+    if (!psMetadataLookupBool (&status, recipe, "GALAXY_SHAPES")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "galaxy shapes were not measured, skipping\n");
+	return true;
+    }
+
+    // create a header to hold the output data
+    psMetadata *outhead = psMetadataAlloc ();
+
+    // write the links to the image header
+    psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "galaxy table extension", extname);
+
+    // let's write these out in S/N order
+    sources = psArraySort (sources, pmSourceSortByFlux);
+
+    psArray *table = psArrayAllocEmpty (sources->n);
+
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *thisSource = sources->data[i];
+
+	// this is the "real" version of this source 
+	pmSource *source = thisSource->parent ? thisSource->parent : thisSource;
+
+	// if we did not fit the galaxy model, modelFits will be NULL
+        if (source->modelFits == NULL) continue;
+
+	// if we did not fit the galaxy model, galaxyFits will also be NULL
+        if (source->galaxyFits == NULL) continue;
+
+	pmModel *model = source->modelFits->data[0];
+	if (!model) return false;
+
+	// X,Y coordinates are stored with the model parameters
+ 	psF32 *PAR = model->params->data.F32;
+
+	psMetadata *row = psMetadataAlloc ();
+
+	// we write out the x,y positions so people can link to the psf either way (position or ID)
+	psMetadataAddU32 (row, PS_LIST_TAIL, "IPP_IDET",         0, "IPP detection identifier index", source->seq);
+	psMetadataAddF32 (row, PS_LIST_TAIL, "X_FIT",            0, "model x coordinate",             PAR[PM_PAR_XPOS]);
+	psMetadataAddF32 (row, PS_LIST_TAIL, "Y_FIT",            0, "model y coordinate",             PAR[PM_PAR_YPOS]);
+	psMetadataAddF32 (row, PS_LIST_TAIL, "NPIX",             0, "number of pixels for fits",      source->galaxyFits->nPix);
+
+	psVector *Flux = source->galaxyFits->Flux;
+	psVector *dFlux = source->galaxyFits->dFlux;
+	psVector *chisq = source->galaxyFits->chisq;
+
+	psMetadataAddVector (row, PS_LIST_TAIL, "GAL_FLUX",     PS_META_REPLACE, "normalization for galaxy flux", Flux);
+	psMetadataAddVector (row, PS_LIST_TAIL, "GAL_FLUX_ERR", PS_META_REPLACE, "error on normalization", dFlux);
+	psMetadataAddVector (row, PS_LIST_TAIL, "GAL_CHISQ",    PS_META_REPLACE, "galaxy fit chisq", chisq);
+
+	psArrayAdd (table, 100, row);
+	psFree (row);
+    }
+
+    if (table->n == 0) {
+        if (!psFitsWriteBlank (fits, outhead, extname)) {
+            psError(psErrorCodeLast(), false, "Unable to write empty sources file.");
+            psFree(outhead);
+            psFree(table);
+            return false;
+        }
+        psFree (outhead);
+        psFree (table);
+        return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing galaxy data %s\n", extname);
+    if (!psFitsWriteTable (fits, outhead, table, extname)) {
+        psError(psErrorCodeLast(), false, "writing galaxy data %s\n", extname);
+        psFree (outhead);
+        psFree(table);
+        return false;
+    }
+    psFree (outhead);
+    psFree (table);
+    return true;
+}
+
Index: trunk/psModules/src/objects/pmSourceIO_PS1_CAL_0.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_PS1_CAL_0.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceIO_PS1_CAL_0.c	(revision 36375)
@@ -713,2 +713,7 @@
     return true;
 }
+
+bool pmSourcesWrite_PS1_CAL_0_XGAL (psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+{
+    return true;
+}
Index: trunk/psModules/src/objects/pmSourceIO_PS1_DEV_0.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_PS1_DEV_0.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceIO_PS1_DEV_0.c	(revision 36375)
@@ -255,2 +255,7 @@
     return true;
 }
+
+bool pmSourcesWrite_PS1_DEV_0_XGAL(psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+{
+    return true;
+}
Index: trunk/psModules/src/objects/pmSourceIO_PS1_DEV_1.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_PS1_DEV_1.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceIO_PS1_DEV_1.c	(revision 36375)
@@ -595,2 +595,7 @@
     return true;
 }
+
+bool pmSourcesWrite_PS1_DEV_1_XGAL(psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+{
+    return true;
+}
Index: trunk/psModules/src/objects/pmSourceIO_SMPDATA.c
===================================================================
--- trunk/psModules/src/objects/pmSourceIO_SMPDATA.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceIO_SMPDATA.c	(revision 36375)
@@ -225,2 +225,7 @@
     return true;
 } 
+
+bool pmSourcesWrite_SMPDATA_XGAL(psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+{
+    return true;
+} 
Index: trunk/psModules/src/objects/pmSourceMasks.h
===================================================================
--- trunk/psModules/src/objects/pmSourceMasks.h	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceMasks.h	(revision 36375)
@@ -56,4 +56,19 @@
     PM_SOURCE_MODE2_DIFF_SELF_MATCH  	  = 0x00000800, ///< positive detection match is probably this source 
     PM_SOURCE_MODE2_SATSTAR_PROFILE       = 0x00001000, ///< saturated source is modeled with a radial profile
+
+    PM_SOURCE_MODE2_ECONTOUR_FEW_PTS      = 0x00002000, ///< too few points to measure the elliptical contour
+    PM_SOURCE_MODE2_RADBIN_NAN_CENTER     = 0x00004000, ///< radial bins failed with too many NaN center bin
+    PM_SOURCE_MODE2_PETRO_NAN_CENTER      = 0x00008000, ///< petrosian radial bins failed with too many NaN center bin
+    PM_SOURCE_MODE2_PETRO_NO_PROFILE      = 0x00010000, ///< petrosian not build because radial bins missing
+
+    PM_SOURCE_MODE2_PETRO_INSIG_RATIO     = 0x00020000, ///< insignificant measurement of petrosian ratio
+    PM_SOURCE_MODE2_PETRO_RATIO_ZEROBIN   = 0x00040000, ///< petrosian ratio in the 0th bin (likely bad)
+    
+    PM_SOURCE_MODE2_EXT_FITS_RUN          = 0x00080000, ///< we attempted to run extended fits on this source
+    PM_SOURCE_MODE2_EXT_FITS_FAIL         = 0x00100000, ///< at least one of the model fits failed
+    PM_SOURCE_MODE2_EXT_FITS_RETRY        = 0x00200000, ///< one of the model fits was re-tried with new window
+    PM_SOURCE_MODE2_EXT_FITS_NONE         = 0x00400000, ///< ALL of the model fits failed
+    
+    
 } pmSourceMode2;
 
Index: trunk/psModules/src/objects/pmSourceMoments.c
===================================================================
--- trunk/psModules/src/objects/pmSourceMoments.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourceMoments.c	(revision 36375)
@@ -65,6 +65,9 @@
 void pmSourceMomentsSetVerbose(bool state){ beVerbose = state; }
 
+bool pmSourceMomentsHighOrder    (pmSource *source, float radius, float sigma, float minSN, psImageMaskType maskVal);
+bool pmSourceMomentsRadialMoment (pmSource *source, float radius, float minKronRadius, psImageMaskType maskVal);
+bool pmSourceMomentsKronFluxes   (pmSource *source, float sigma,  float minSN, psImageMaskType maskVal);
+
 // if mode & EXTERNAL or mode2 & MATCHED, do not re-calculate the centroid (use peak as centroid)
-
 bool pmSourceMoments(pmSource *source, float radius, float sigma, float minSN, float minKronRadius, psImageMaskType maskVal)
 {
@@ -74,18 +77,7 @@
     PS_ASSERT_FLOAT_LARGER_THAN(radius, 0.0, false);
 
-    // this function assumes the sky has been well-subtracted for the image
-    float sky = 0.0;
-
     if (source->moments == NULL) {
       source->moments = pmMomentsAlloc();
     }
-
-    float Sum = 0.0;
-    float Var = 0.0;
-    float SumCore = 0.0;
-    float VarCore = 0.0;
-    float R2 = PS_SQR(radius);
-    float minSN2 = PS_SQR(minSN);
-    float rsigma2 = 0.5 / PS_SQR(sigma);
 
     // a note about coordinates: coordinates of objects throughout psphot refer to the primary
@@ -110,10 +102,229 @@
     // of any object drops pretty quickly outside 1-2 sigmas.  (The exception is bright
     // saturated stars, for which we need to use a very large radius here)
+    // NOTE: if (mode & EXTERNAL) or (mode2 & MATCHED), do not re-calculate the centroid (use peak as centroid)
+    // (we still call this function because it sets moments->Sum,SN,Peak,nPixels
     if (!pmSourceMomentsGetCentroid (source, 1.5*sigma, 0.0, minSN, maskVal, source->peak->xf, source->peak->yf)) {
 	return false;
     }
 
+    pmSourceMomentsHighOrder (source, radius, sigma, minSN, maskVal);
+
+    // now calculate the 1st radial moment (for kron flux) using symmetrical averaging
+    pmSourceMomentsRadialMoment (source, radius, minKronRadius, maskVal);
+
+    // now calculate the kron flux values using the 1st radial moment
+    pmSourceMomentsKronFluxes (source, sigma, minSN, maskVal);
+
+    psTrace ("psModules.objects", 4, "Mrf: %f  KronFlux: %f  Mxx: %f  Mxy: %f  Myy: %f  Mxxx: %f  Mxxy: %f  Mxyy: %f  Myyy: %f  Mxxxx: %f  Mxxxy: %f  Mxxyy: %f  Mxyyy: %f  Mxyyy: %f\n",
+	     source->moments->Mrf,   source->moments->KronFlux, 
+	     source->moments->Mxx,   source->moments->Mxy,   source->moments->Myy,
+	     source->moments->Mxxx,  source->moments->Mxxy,  source->moments->Mxyy,  source->moments->Myyy,
+	     source->moments->Mxxxx, source->moments->Mxxxy, source->moments->Mxxyy, source->moments->Mxyyy, source->moments->Myyyy);
+
+    psTrace ("psModules.objects", 3, "peak %f %f (%f = %f) Mx: %f  My: %f  Sum: %f  Mxx: %f  Mxy: %f  Myy: %f  Npix: %d\n",
+	     source->peak->xf, source->peak->yf, 
+	     source->peak->rawFlux, sqrt(source->peak->detValue), 
+	     source->moments->Mx, source->moments->My, 
+	     source->moments->Sum, 
+	     source->moments->Mxx, source->moments->Mxy, source->moments->Myy, 
+	     source->moments->nPixels);
+
+    return(true);
+}
+
+bool pmSourceMomentsGetCentroid(pmSource *source, float radius, float sigma, float minSN, psImageMaskType maskVal, float xGuess, float yGuess) { 
+
+    // First Pass: calculate the first moments (these are subtracted from the coordinates below)
+    // Sum = SUM (z - sky)
+    // X1  = SUM (x - xc)*(z - sky)
+    // .. etc
+
+    float sky = 0.0;
+
+    float peakPixel = -PS_MAX_F32;
+    psS32 numPixels = 0;
+    float Sum = 0.0;
+    float Var = 0.0;
+    float X1 = 0.0;
+    float Y1 = 0.0;
+    float R2 = PS_SQR(radius);
+    float minSN2 = PS_SQR(minSN);
+    float rsigma2 = 0.5 / PS_SQR(sigma);
+
+    float xPeak = xGuess - source->pixels->col0; // coord of peak in subimage
+    float yPeak = yGuess - source->pixels->row0; // coord of peak in subimage
+
+    // we are guaranteed to have a valid pixel and variance at this location (right? right?)
+    // float weightNorm = source->pixels->data.F32[yPeak][xPeak] / sqrt (source->variance->data.F32[yPeak][xPeak]);
+    // psAssert (isfinite(source->pixels->data.F32[yPeak][xPeak]), "peak must be on valid pixel");
+    // psAssert (isfinite(source->variance->data.F32[yPeak][xPeak]), "peak must be on valid pixel");
+    // psAssert (source->variance->data.F32[yPeak][xPeak] > 0, "peak must be on valid pixel");
+
+    // the moments [Sum(x*f) / Sum(f)] are calculated in pixel index values, and should
+    // not depend on the fractional pixel location of the source.  However, the aperture
+    // (radius) and the Gaussian window (sigma) depend subtly on the fractional pixel
+    // position of the expected centroid
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+
+	float yDiff = row + 0.5 - yPeak;
+	if (fabs(yDiff) > radius) continue;
+
+	float *vPix = source->pixels->data.F32[row];
+	float *vWgt = source->variance ? source->variance->data.F32[row] : source->pixels->data.F32[row];
+
+	psImageMaskType *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[row];
+	// psImageMaskType *vMsk = (source->maskView == NULL) ? NULL : source->maskView->data.PS_TYPE_IMAGE_MASK_DATA[row];
+
+	for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
+	    if (vMsk) {
+		if (*vMsk & maskVal) {
+		    vMsk++;
+		    continue;
+		}
+		vMsk++;
+	    }
+	    if (isnan(*vPix)) continue;
+
+	    float xDiff = col + 0.5 - xPeak;
+	    if (fabs(xDiff) > radius) continue;
+
+	    // radius is just a function of (xDiff, yDiff)
+	    float r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
+	    if (r2 > R2) continue;
+
+	    float pDiff = *vPix - sky;
+	    float wDiff = *vWgt;
+
+	    // skip pixels below specified significance level.  for a PSFs, this
+	    // over-weights the wings of bright stars compared to those of faint stars.
+	    // for the estimator used for extended source analysis (where the window
+	    // function is allowed to be arbitrarily large), we need to clip to avoid
+	    // negative second moments.
+	    if (PS_SQR(pDiff) < minSN2*wDiff) continue; // 
+	    if ((minSN > 0.0) && (pDiff < 0)) continue; // 
+
+	    // Apply a Gaussian window function.  Be careful with the window function.  S/N
+	    // weighting over weights the sky for faint sources
+	    if (sigma > 0.0) {
+		float z  = r2*rsigma2;
+		assert (z >= 0.0);
+		float weight  = exp(-z);
+
+		wDiff *= weight;
+		pDiff *= weight;
+	    } 
+
+	    Var += wDiff;
+	    Sum += pDiff;
+
+	    float xWght = xDiff * pDiff;
+	    float yWght = yDiff * pDiff;
+
+	    X1  += xWght;
+	    Y1  += yWght;
+
+	    peakPixel = PS_MAX (*vPix, peakPixel);
+	    numPixels++;
+	}
+    }
+
+    // if we have less than (1/4) of the possible pixels (in circle or box), force a retry
+    int minPixels = PS_MIN(0.75*R2, source->pixels->numCols*source->pixels->numRows/4.0);
+
+    // XXX EAM - the limit is a bit arbitrary.  make it user defined?
+    if ((numPixels < minPixels) || (Sum <= 0)) {
+	psTrace ("psModules.objects", 3, "insufficient valid pixels (%d vs %d; %f) for source\n", numPixels, minPixels, Sum);
+	return (false);
+    }
+
+    // calculate the first moment.
+    float Mx = X1/Sum;
+    float My = Y1/Sum;
+    if ((fabs(Mx) > radius) || (fabs(My) > radius)) {
+	psTrace ("psModules.objects", 3, "extreme centroid swing; invalid peak %d, %d\n", source->peak->x, source->peak->y);
+	return (false);
+    }
+    if ((fabs(Mx) > 2.0) || (fabs(My) > 2.0)) {
+	psTrace ("psModules.objects", 3, " big centroid swing; ok peak? %d, %d\n", source->peak->x, source->peak->y);
+    }
+
+    psTrace ("psModules.objects", 5, "id: %d, sky: %f  Mx: %f  My: %f  Sum: %f  X1: %f  Y1: %f  Npix: %d\n", source->id, sky, Mx, My, Sum, X1, Y1, numPixels);
+
+    // add back offset of peak in primary image
+    // also offset from pixel index to pixel coordinate
+    // (the calculation above uses pixel index instead of coordinate)
+    // 0.5 PIX: moments are calculated using the pixel index and converted here to pixel coords
+
+    // we only update the centroid if the position is not supplied from elsewhere
+    bool skipCentroid = false;
+    skipCentroid |= (source->mode  & PM_SOURCE_MODE_EXTERNAL); // skip externally supplied positions
+    skipCentroid |= (source->mode2 & PM_SOURCE_MODE2_MATCHED); // skip sources defined by other image positions
+
+    if (skipCentroid) {
+	source->moments->Mx = source->peak->xf;
+	source->moments->My = source->peak->yf;
+    } else {
+	source->moments->Mx = Mx + xGuess;
+	source->moments->My = My + yGuess;
+    }
+
+    source->moments->Sum = Sum;
+    source->moments->SN  = Sum / sqrt(Var);
+    source->moments->Peak = peakPixel;
+    source->moments->nPixels = numPixels;
+
+    return true;
+}
+
+float pmSourceMinKronRadius(psArray *sources, float PSF_SN_LIM) {
+
+    psVector *radii = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *src = sources->data[i]; // Source of interest
+	if (!src || !src->moments) {
+	    continue;
+	}
+
+	if (src->mode & PM_SOURCE_MODE_BLEND) {
+	    continue;
+	}
+
+	if (!src->moments->nPixels) continue;
+
+	if (src->moments->SN < PSF_SN_LIM) continue;
+
+	// XXX put in Mxx,Myy cut based on clump location
+
+	psVectorAppend(radii, src->moments->Mrf);
+    }
+
+    // find the peak in this image
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+
+    if (!psVectorStats (stats, radii, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
+	psFree(stats);
+	return NAN;
+    }
+
+    float minRadius = stats->sampleMedian;
+
+    psFree(radii);
+    psFree(stats);
+    return minRadius;
+}
+
+bool pmSourceMomentsHighOrder (pmSource *source, float radius, float sigma, float minSN, psImageMaskType maskVal) {
+
+    // this function assumes the sky has been well-subtracted for the image
+    float Sum = 0.0;
+    float R2 = PS_SQR(radius);
+    float minSN2 = PS_SQR(minSN);
+    float rsigma2 = 0.5 / PS_SQR(sigma);
+
     // Now calculate higher-order moments, using the above-calculated first moments to adjust coordinates
-    // Xn  = SUM (x - xc)^n * (z - sky)
+    // Xn  = SUM (x - xc)^n * (z - sky) -- note that sky is 0.0 by definition here
     float XX = 0.0;
     float XY = 0.0;
@@ -129,11 +340,4 @@
     float YYYY = 0.0;
 
-    Sum = 0.0;  // the second pass may include slightly different pixels, re-determine Sum
-
-    // float dX = source->moments->Mx - source->peak->xf;
-    // float dY = source->moments->My - source->peak->yf;
-    // float dR = hypot(dX, dY);
-    // float Xo = (dR < 2.0) ? source->moments->Mx : source->peak->xf;
-    // float Yo = (dR < 2.0) ? source->moments->My : source->peak->yf;
     float Xo = source->moments->Mx;
     float Yo = source->moments->My;
@@ -154,5 +358,4 @@
 
 	psImageMaskType  *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[row];
-	// psImageMaskType  *vMsk = (source->maskView == NULL) ? NULL : source->maskView->data.PS_TYPE_IMAGE_MASK_DATA[row];
 
 	for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
@@ -173,5 +376,5 @@
 	    if (r2 > R2) continue;
 
-	    float fDiff = *vPix - sky;
+	    float fDiff = *vPix;
 	    float pDiff = fDiff;
 	    float wDiff = *vWgt;
@@ -181,8 +384,8 @@
 	    // stars.
 	    if (PS_SQR(pDiff) < minSN2*wDiff) continue;
-	    if ((minSN > 0.0) && (pDiff < 0)) continue; // 
+	    if ((minSN > 0.0) && (pDiff < 0)) continue; 
 
 	    // Apply a Gaussian window function.  Be careful with the window function.  S/N
-	    // weighting over weights the sky for faint sources
+	    // weighting over-weights the sky for faint sources
 	    if (sigma > 0.0) {
 		float z = r2 * rsigma2;
@@ -230,20 +433,4 @@
 	    XYYY  += xyyy;
 	    YYYY  += yyyy;
-
-	    // Kron Flux uses the 1st radial moment (NOT Gaussian windowed?)
-	    // XXX float r = sqrt(r2);
-	    // XXX float rf = r * fDiff;
-	    // XXX float rh = sqrt(r) * fDiff;
-	    // XXX float rs = fDiff;
-	    // XXX 
-	    // XXX float rfw = r * pDiff;
-	    // XXX float rhw = sqrt(r) * pDiff;
-	    // XXX 
-	    // XXX RF  += rf;
-	    // XXX RH  += rh;
-	    // XXX RS  += rs;
-	    // XXX 
-	    // XXX RFW  += rfw;
-	    // XXX RHW  += rhw;
 	}
     }
@@ -263,8 +450,11 @@
     source->moments->Myyyy = YYYY/Sum;
 
-    // *** now calculate the 1st radial moment (for kron flux) -- symmetrical averaging
+    return true;
+}
+
+bool pmSourceMomentsRadialMoment (pmSource *source, float radius, float minKronRadius, psImageMaskType maskVal) {
+
 
     float **vPix = source->pixels->data.F32;
-    float **vWgt = source->variance ? source->variance->data.F32 : source->pixels->data.F32;
     psImageMaskType **vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
 
@@ -272,4 +462,15 @@
     float RH = 0.0;
     float RS = 0.0;
+
+    // centroid around which to calculate the moments
+    float Xo = source->moments->Mx;
+    float Yo = source->moments->My;
+
+    // center of mass in subimage.  Note: the calculation below uses pixel index, so we correct
+    // xCM, yCM from pixel coords to pixel index here.
+    float xCM = Xo - 0.5 - source->pixels->col0; // coord of peak in subimage
+    float yCM = Yo - 0.5 - source->pixels->row0; // coord of peak in subimage
+
+    float R2 = PS_SQR(radius);
 
     for (psS32 row = 0; row < source->pixels->numRows ; row++) {
@@ -304,6 +505,6 @@
 	    if (r2 > R2) continue;
 
-	    float fDiff1 = vPix[row][col] - sky;
-	    float fDiff2 = vPix[yFlip][xFlip] - sky;
+	    float fDiff1 = vPix[row][col];
+	    float fDiff2 = vPix[yFlip][xFlip];
 	    float pDiff = (fDiff1 > 0.0) ? sqrt(fabs(fDiff1*fDiff2)) : -sqrt(fabs(fDiff1*fDiff2));
 
@@ -329,11 +530,22 @@
 	kronRefRadius = MIN(radius, kronRefRadius);
     }
-    source->moments->Mrf = kronRefRadius;
-
-    // *** now calculate the kron flux values using the 1st radial moment
-
-    float radKinner = 1.0*kronRefRadius;
-    float radKron   = 2.5*kronRefRadius;
-    float radKouter = 4.0*kronRefRadius;
+
+    // if source is externally supplied and it already has a finite Mrf do not change it
+    if (! ((source->mode & PM_SOURCE_MODE_EXTERNAL) && isfinite(source->moments->Mrf))) {
+        source->moments->Mrf = kronRefRadius;
+    }
+
+    return true;
+}
+
+bool pmSourceMomentsKronFluxes (pmSource *source, float sigma, float minSN, psImageMaskType maskVal) {
+
+    float **vPix = source->pixels->data.F32;
+    float **vWgt = source->variance ? source->variance->data.F32 : source->pixels->data.F32;
+    psImageMaskType **vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
+
+    float radKinner = 1.0*source->moments->Mrf;
+    float radKron   = 2.5*source->moments->Mrf;
+    float radKouter = 4.0*source->moments->Mrf;
 
     int nKronPix = 0;
@@ -341,7 +553,22 @@
     int nInner = 0;
     int nOuter = 0;
-    Sum = Var = 0.0;
+    
+    float Sum = 0.0;
+    float Var = 0.0;
+    float SumCore = 0.0;
+    float VarCore = 0.0;
     float SumInner = 0.0;
     float SumOuter = 0.0;
+
+    // centroid around which to calculate the moments
+    float Xo = source->moments->Mx;
+    float Yo = source->moments->My;
+
+    // center of mass in subimage.  Note: the calculation below uses pixel index, so we correct
+    // xCM, yCM from pixel coords to pixel index here.
+    float xCM = Xo - 0.5 - source->pixels->col0; // coord of peak in subimage
+    float yCM = Yo - 0.5 - source->pixels->row0; // coord of peak in subimage
+
+    float minSN2 = PS_SQR(minSN);
 
     // calculate the Kron flux, and related fluxes (NO symmetrical averaging)
@@ -362,5 +589,5 @@
 	    float r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
 
-	    float fDiff1 = vPix[row][col] - sky;
+	    float fDiff1 = vPix[row][col];
 	    float pDiff = fDiff1;
 	    float wDiff = vWgt[row][col];
@@ -376,5 +603,4 @@
 		Var += wDiff;
 		nKronPix ++;
-		// if (beVerbose) fprintf (stderr, "mome: %d %d  %f  %f  %f\n", col, row, sky, *vPix, Sum);
 	    }
 
@@ -397,10 +623,10 @@
     }
     // *** should I rescale these fluxes by pi R^2 / nNpix?
-    // XXX source->moments->KronCore    = SumCore       * M_PI * PS_SQR(sigma) / nCorePix;
-    // XXX source->moments->KronCoreErr = sqrt(VarCore) * M_PI * PS_SQR(sigma) / nCorePix;
-    // XXX source->moments->KronFlux    = Sum       * M_PI * PS_SQR(radKron) / nKronPix;
-    // XXX source->moments->KronFluxErr = sqrt(Var) * M_PI * PS_SQR(radKron) / nKronPix;
-    // XXX source->moments->KronFinner = SumInner * M_PI * (PS_SQR(radKron)   - PS_SQR(radKinner)) / nInner;
-    // XXX source->moments->KronFouter = SumOuter * M_PI * (PS_SQR(radKouter) -   PS_SQR(radKron)) / nOuter;
+    // XXX source->moments->KronCore    = SumCore       * M_PI *  PS_SQR(sigma) / nCorePix;
+    // XXX source->moments->KronCoreErr = sqrt(VarCore) * M_PI *  PS_SQR(sigma) / nCorePix;
+    // XXX source->moments->KronFlux    = Sum           * M_PI *  PS_SQR(radKron) / nKronPix;
+    // XXX source->moments->KronFluxErr = sqrt(Var)     * M_PI *  PS_SQR(radKron) / nKronPix;
+    // XXX source->moments->KronFinner  = SumInner      * M_PI * (PS_SQR(radKron)   - PS_SQR(radKinner)) / nInner;
+    // XXX source->moments->KronFouter  = SumOuter      * M_PI * (PS_SQR(radKouter) -   PS_SQR(radKron)) / nOuter;
 
     source->moments->KronCore    = SumCore;
@@ -408,6 +634,6 @@
     source->moments->KronFlux    = Sum;
     source->moments->KronFluxErr = sqrt(Var);
-    source->moments->KronFinner = SumInner;
-    source->moments->KronFouter = SumOuter;
+    source->moments->KronFinner  = SumInner;
+    source->moments->KronFouter  = SumOuter;
 
     // XXX not sure I should save this here...
@@ -416,198 +642,4 @@
     source->moments->KronRadiusPSF  = source->moments->Mrf;
 
-    psTrace ("psModules.objects", 4, "Mrf: %f  KronFlux: %f  Mxx: %f  Mxy: %f  Myy: %f  Mxxx: %f  Mxxy: %f  Mxyy: %f  Myyy: %f  Mxxxx: %f  Mxxxy: %f  Mxxyy: %f  Mxyyy: %f  Mxyyy: %f\n",
-	     source->moments->Mrf,   source->moments->KronFlux, 
-	     source->moments->Mxx,   source->moments->Mxy,   source->moments->Myy,
-	     source->moments->Mxxx,  source->moments->Mxxy,  source->moments->Mxyy,  source->moments->Myyy,
-	     source->moments->Mxxxx, source->moments->Mxxxy, source->moments->Mxxyy, source->moments->Mxyyy, source->moments->Myyyy);
-
-    psTrace ("psModules.objects", 3, "peak %f %f (%f = %f) Mx: %f  My: %f  Sum: %f  Mxx: %f  Mxy: %f  Myy: %f  sky: %f  Npix: %d\n",
-	     source->peak->xf, source->peak->yf, source->peak->rawFlux, sqrt(source->peak->detValue), source->moments->Mx,   source->moments->My, Sum, source->moments->Mxx,   source->moments->Mxy,   source->moments->Myy, sky, source->moments->nPixels);
-
-    return(true);
-}
-
-bool pmSourceMomentsGetCentroid(pmSource *source, float radius, float sigma, float minSN, psImageMaskType maskVal, float xGuess, float yGuess) { 
-
-    // First Pass: calculate the first moments (these are subtracted from the coordinates below)
-    // Sum = SUM (z - sky)
-    // X1  = SUM (x - xc)*(z - sky)
-    // .. etc
-
-    float sky = 0.0;
-
-    float peakPixel = -PS_MAX_F32;
-    psS32 numPixels = 0;
-    float Sum = 0.0;
-    float Var = 0.0;
-    float X1 = 0.0;
-    float Y1 = 0.0;
-    float R2 = PS_SQR(radius);
-    float minSN2 = PS_SQR(minSN);
-    float rsigma2 = 0.5 / PS_SQR(sigma);
-
-    float xPeak = xGuess - source->pixels->col0; // coord of peak in subimage
-    float yPeak = yGuess - source->pixels->row0; // coord of peak in subimage
-
-    // we are guaranteed to have a valid pixel and variance at this location (right? right?)
-    // float weightNorm = source->pixels->data.F32[yPeak][xPeak] / sqrt (source->variance->data.F32[yPeak][xPeak]);
-    // psAssert (isfinite(source->pixels->data.F32[yPeak][xPeak]), "peak must be on valid pixel");
-    // psAssert (isfinite(source->variance->data.F32[yPeak][xPeak]), "peak must be on valid pixel");
-    // psAssert (source->variance->data.F32[yPeak][xPeak] > 0, "peak must be on valid pixel");
-
-    // the moments [Sum(x*f) / Sum(f)] are calculated in pixel index values, and should
-    // not depend on the fractional pixel location of the source.  However, the aperture
-    // (radius) and the Gaussian window (sigma) depend subtly on the fractional pixel
-    // position of the expected centroid
-
-    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
-
-	float yDiff = row + 0.5 - yPeak;
-	if (fabs(yDiff) > radius) continue;
-
-	float *vPix = source->pixels->data.F32[row];
-	float *vWgt = source->variance ? source->variance->data.F32[row] : source->pixels->data.F32[row];
-
-	psImageMaskType *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[row];
-	// psImageMaskType *vMsk = (source->maskView == NULL) ? NULL : source->maskView->data.PS_TYPE_IMAGE_MASK_DATA[row];
-
-	for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
-	    if (vMsk) {
-		if (*vMsk & maskVal) {
-		    vMsk++;
-		    continue;
-		}
-		vMsk++;
-	    }
-	    if (isnan(*vPix)) continue;
-
-	    float xDiff = col + 0.5 - xPeak;
-	    if (fabs(xDiff) > radius) continue;
-
-	    // radius is just a function of (xDiff, yDiff)
-	    float r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
-	    if (r2 > R2) continue;
-
-	    float pDiff = *vPix - sky;
-	    float wDiff = *vWgt;
-
-	    // skip pixels below specified significance level.  for a PSFs, this
-	    // over-weights the wings of bright stars compared to those of faint stars.
-	    // for the estimator used for extended source analysis (where the window
-	    // function is allowed to be arbitrarily large), we need to clip to avoid
-	    // negative second moments.
-	    if (PS_SQR(pDiff) < minSN2*wDiff) continue; // 
-	    if ((minSN > 0.0) && (pDiff < 0)) continue; // 
-
-	    // Apply a Gaussian window function.  Be careful with the window function.  S/N
-	    // weighting over weights the sky for faint sources
-	    if (sigma > 0.0) {
-		float z  = r2*rsigma2;
-		assert (z >= 0.0);
-		float weight  = exp(-z);
-
-		wDiff *= weight;
-		pDiff *= weight;
-	    } 
-
-	    Var += wDiff;
-	    Sum += pDiff;
-
-	    float xWght = xDiff * pDiff;
-	    float yWght = yDiff * pDiff;
-
-	    X1  += xWght;
-	    Y1  += yWght;
-
-	    peakPixel = PS_MAX (*vPix, peakPixel);
-	    numPixels++;
-	}
-    }
-
-    // if we have less than (1/4) of the possible pixels (in circle or box), force a retry
-    int minPixels = PS_MIN(0.75*R2, source->pixels->numCols*source->pixels->numRows/4.0);
-
-    // XXX EAM - the limit is a bit arbitrary.  make it user defined?
-    if ((numPixels < minPixels) || (Sum <= 0)) {
-	psTrace ("psModules.objects", 3, "insufficient valid pixels (%d vs %d; %f) for source\n", numPixels, minPixels, Sum);
-	return (false);
-    }
-
-    // calculate the first moment.
-    float Mx = X1/Sum;
-    float My = Y1/Sum;
-    if ((fabs(Mx) > radius) || (fabs(My) > radius)) {
-	psTrace ("psModules.objects", 3, "extreme centroid swing; invalid peak %d, %d\n", source->peak->x, source->peak->y);
-	return (false);
-    }
-    if ((fabs(Mx) > 2.0) || (fabs(My) > 2.0)) {
-	psTrace ("psModules.objects", 3, " big centroid swing; ok peak? %d, %d\n", source->peak->x, source->peak->y);
-    }
-
-    psTrace ("psModules.objects", 5, "id: %d, sky: %f  Mx: %f  My: %f  Sum: %f  X1: %f  Y1: %f  Npix: %d\n", source->id, sky, Mx, My, Sum, X1, Y1, numPixels);
-
-    // add back offset of peak in primary image
-    // also offset from pixel index to pixel coordinate
-    // (the calculation above uses pixel index instead of coordinate)
-    // 0.5 PIX: moments are calculated using the pixel index and converted here to pixel coords
-
-    // we only update the centroid if the position is not supplied from elsewhere
-    bool skipCentroid = false;
-    skipCentroid |= (source->mode  & PM_SOURCE_MODE_EXTERNAL); // skip externally supplied positions
-    skipCentroid |= (source->mode2 & PM_SOURCE_MODE2_MATCHED); // skip sources defined by other image positions
-
-    if (skipCentroid) {
-	source->moments->Mx = source->peak->xf;
-	source->moments->My = source->peak->yf;
-    } else {
-	source->moments->Mx = Mx + xGuess;
-	source->moments->My = My + yGuess;
-    }
-
-    source->moments->Sum = Sum;
-    source->moments->SN  = Sum / sqrt(Var);
-    source->moments->Peak = peakPixel;
-    source->moments->nPixels = numPixels;
-
     return true;
 }
-
-float pmSourceMinKronRadius(psArray *sources, float PSF_SN_LIM) {
-
-    psVector *radii = psVectorAllocEmpty(100, PS_TYPE_F32);
-
-    for (int i = 0; i < sources->n; i++) {
-	pmSource *src = sources->data[i]; // Source of interest
-	if (!src || !src->moments) {
-	    continue;
-	}
-
-	if (src->mode & PM_SOURCE_MODE_BLEND) {
-	    continue;
-	}
-
-	if (!src->moments->nPixels) continue;
-
-	if (src->moments->SN < PSF_SN_LIM) continue;
-
-	// XXX put in Mxx,Myy cut based on clump location
-
-	psVectorAppend(radii, src->moments->Mrf);
-    }
-
-    // find the peak in this image
-    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
-
-    if (!psVectorStats (stats, radii, NULL, NULL, 0)) {
-	psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
-	psFree(stats);
-	return NAN;
-    }
-
-    float minRadius = stats->sampleMedian;
-
-    psFree(radii);
-    psFree(stats);
-    return minRadius;
-}
-
Index: trunk/psModules/src/objects/pmSourcePhotometry.c
===================================================================
--- trunk/psModules/src/objects/pmSourcePhotometry.c	(revision 36374)
+++ trunk/psModules/src/objects/pmSourcePhotometry.c	(revision 36375)
@@ -113,4 +113,12 @@
     source->apFluxErr = NAN;
 
+    pmModelStatus badModel = PM_MODEL_STATUS_NONE;
+    badModel |= PM_MODEL_STATUS_BADARGS;
+    badModel |= PM_MODEL_STATUS_OFFIMAGE;
+    badModel |= PM_MODEL_STATUS_NAN_CHISQ;
+    badModel |= PM_MODEL_SERSIC_PCM_FAIL_GUESS;
+    badModel |= PM_MODEL_SERSIC_PCM_FAIL_GRID;
+    badModel |= PM_MODEL_PCM_FAIL_GUESS;
+
     // XXXXXX review:
     // Select the 'best' model -- this is used for PSF_QF,_PERFECT & ???. isPSF is true if this
@@ -162,5 +170,5 @@
         for (int i = 0; i < source->modelFits->n; i++) {
             pmModel *model = source->modelFits->data[i];
-	    if (model->flags & PM_MODEL_STATUS_BADARGS) continue;
+	    if (model->flags & badModel) continue;
             status = pmSourcePhotometryModel (&model->mag, NULL, model);
             if (model == source->modelEXT) foundEXT = true;
@@ -902,4 +910,40 @@
 }
 
+bool pmSourceChisqModelFlux (pmSource *source, pmModel *model, psImageMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(model, false);
+
+    float dC = 0.0;
+    int Npix = 0;
+
+    psVector *params = model->params;
+    psImage  *image = source->pixels;
+    psImage  *modelFlux = source->modelFlux;
+    psImage  *mask = source->maskObj;
+    psImage  *variance = source->variance;
+
+    float Io = params->data.F32[PM_PAR_I0];
+
+    for (int iy = 0; iy < image->numRows; iy++) {
+        for (int ix = 0; ix < image->numCols; ix++) {
+
+	    // skip pixels which are masked
+            if (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & maskVal) continue;
+
+            if (variance->data.F32[iy][ix] <= 0) continue;
+
+            dC += PS_SQR (image->data.F32[iy][ix] - Io*modelFlux->data.F32[iy][ix]) / variance->data.F32[iy][ix];
+            Npix ++;
+        }
+    }
+    model->nPix = Npix;
+    model->nDOF = Npix - model->nPar;
+    model->chisq = dC;
+    model->chisqNorm = dC / model->nDOF;
+
+    return (true);
+}
+
 double pmSourceModelWeight(const pmSource *Mi, int term, const pmSourceFitVarMode fitVarMode, const float covarFactor, psImageMaskType maskVal)
 {
Index: trunk/psphot/src/Makefile.am
===================================================================
--- trunk/psphot/src/Makefile.am	(revision 36374)
+++ trunk/psphot/src/Makefile.am	(revision 36375)
@@ -25,5 +25,5 @@
 libpsphot_la_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 
-bin_PROGRAMS = psphot psphotForced psphotMinimal psphotMakePSF psphotStack psphotModelTest
+bin_PROGRAMS = psphot psphotForced psphotFullForce psphotMinimal psphotMakePSF psphotStack psphotModelTest psmakecff
 # bin_PROGRAMS = psphotPetrosianStudy psphotTest psphotMomentsStudy 
 
@@ -36,4 +36,8 @@
 psphotForced_LDADD = libpsphot.la
 
+psphotFullForce_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psphotFullForce_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psphotFullForce_LDADD = libpsphot.la
+
 psphotMinimal_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
 psphotMinimal_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
@@ -51,4 +55,9 @@
 psphotModelTest_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
 psphotModelTest_LDADD = libpsphot.la
+
+psmakecff_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psmakecff_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psmakecff_LDADD = libpsphot.la
+
 
 # psphotMomentsStudy_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
@@ -77,4 +86,13 @@
         psphotForced.c             \
 	psphotForcedArguments.c	   \
+	psphotParseCamera.c        \
+	psphotImageLoop.c	   \
+	psphotMosaicChip.c	   \
+	psphotCleanup.c
+
+# generalized forced photometry (including Kron, Petro, and Models) of specified positions given a specified psf
+psphotFullForce_SOURCES = \
+        psphotFullForce.c             \
+	psphotFullForceArguments.c    \
 	psphotParseCamera.c        \
 	psphotImageLoop.c	   \
@@ -129,4 +147,8 @@
 	psphotCleanup.c
 
+# a program that takes a cmf file and makes a cff file (input file for psphotFullForce
+psmakecff_SOURCES = \
+	psmakecff.c
+
 # psphotTest_SOURCES = \
 #         psphotTest.c
@@ -155,4 +177,6 @@
 	psphotReadoutMinimal.c	       \
 	psphotForcedReadout.c	       \
+	psphotFullForceReadout.c       \
+	psphotFullForce.SourceStats.c \
 	psphotMakePSFReadout.c	       \
 	psphotModelBackground.c	       \
@@ -181,4 +205,5 @@
 	psphotSourceFits.c	       \
 	psphotRadiusChecks.c	       \
+	psphotChooseAnalysisOptions.c  \
 	psphotOutput.c		       \
 	psphotFakeSources.c	       \
@@ -198,4 +223,7 @@
 	psphotRadialPlot.c	       \
 	psphotKronIterate.c            \
+	psphotKronFlux.c            \
+	psphotPetroFlux.c            \
+	psphotGalaxyShape.c            \
 	psphotRadialProfileWings.c     \
 	psphotDeblendSatstars.c	       \
Index: trunk/psphot/src/psmakecff.c
===================================================================
--- trunk/psphot/src/psmakecff.c	(revision 36375)
+++ trunk/psphot/src/psmakecff.c	(revision 36375)
@@ -0,0 +1,171 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+static pmConfig* psmakecffArguments(int, char**);
+static bool psmakecffParseCamera(pmConfig *);
+static bool psmakecffImageLoop(pmConfig*);
+
+int main (int argc, char **argv) {
+
+    psMemInit();
+    psTimerStart ("complete");
+    pmErrorRegister();                  // register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psmakecffArguments (argc, argv);
+    assert(config);
+
+//    psphotVersionPrint();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psmakecffParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (1);
+    }
+
+    // call psphot for each readout
+    if (!psmakecffImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit(1);
+    }
+
+//    psLogMsg ("psphot", PS_LOG_WARN, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+//    psErrorCode exit_status = psphotGetExitStatus();
+//    psphotCleanup (config);
+    exit (0);
+}
+
+// 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
+
+void usage() {
+    fprintf(stderr, "usage: psmakecff -sources <input cmf> <output>\n");
+    exit (1);
+}
+
+static pmConfig* psmakecffArguments(int argc, char **argv) {
+
+    pmConfig *config =  pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    int N;
+
+    if (config == NULL) {
+      psErrorStackPrint(stderr, "Can't read site configuration");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+    if ((N = psArgumentGet (argc, argv, "-sources"))) {
+        pmConfigFileSetsMD(config->arguments, &argc, argv, "SOURCES", "-sources", "-sourceslist");
+    } else {
+        usage();
+    }
+    if (argc < 2) {
+        usage();
+    }
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", PS_DATA_STRING, "", argv[1]);
+
+    return config;
+}
+static bool psmakecffParseCamera(pmConfig *config) {
+    bool status = false;
+
+    pmFPAfile *sources = pmFPAfileDefineFromArgs(&status, config, "PSPHOT.INPUT.CMF", "SOURCES");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to build FPA from sources file");
+        return false;
+    }
+
+    pmFPAfile *output = pmFPAfileDefineOutputFromFile (config, sources, "PSPHOT.OUTPUT.CFF");
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "failed to build output FPA");
+        return false;
+    }
+    output->save = true;
+
+    return true;
+}
+
+# define ESCAPE(MESSAGE) {				\
+	psError(PSPHOT_ERR_DATA, false, MESSAGE);	        \
+	psFree (view);					\
+	return false;					\
+    }
+
+bool psmakecffImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT.CMF");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+        return false;
+    }
+
+    pmFPAfile *output = psMetadataLookupPtr (&status, config->files, "PSPHOT.OUTPUT.CFF");
+    if (!output) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find output data!");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmHDU *lastHDU = NULL;              // Last HDU updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+//    psImageMaskType maskTest = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+
+    // for psphot, we force data to be read at the chip level
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psLogMsg ("psmakecmf", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (! chip->process || ! chip->file_exists) { continue; }
+
+#ifdef notdef
+        pmFPAfileActivate (config->files, false, NULL);
+        pmFPAfileActivate (config->files, true, "PSPHOT.LOAD");
+#endif
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psmakecff.");
+
+        // there is now only a single chip (multiple readouts?). loop over it and process
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psmakecmf", 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 ("psmakecmf", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (! readout->data_exists) { continue; }
+
+		pmHDU *hdu = pmHDUGetHighest(input->fpa, chip, cell);
+		if (hdu && hdu != lastHDU) {
+		    // psphotVersionHeaderFull(hdu->header);
+		    lastHDU = hdu;
+                }
+            }
+
+        }
+        // Defer output and closing of files until we've (possibly) done the NFrames analysis below
+//        pmFPAfileActivate (config->files, false, NULL);
+
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed pmFPAfileIOChecks for Chip in psmakecff.");
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed pmFPAfileIOChecks 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;
+}
Index: trunk/psphot/src/psphot.h
===================================================================
--- trunk/psphot/src/psphot.h	(revision 36374)
+++ trunk/psphot/src/psphot.h	(revision 36375)
@@ -18,4 +18,5 @@
     PSPHOT_SINGLE,
     PSPHOT_FORCED,
+    PSPHOT_FULL_FORCE,
     PSPHOT_MAKE_PSF,
     PSPHOT_MODEL_TEST,
@@ -81,4 +82,7 @@
 bool            psphotSourceStatsReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, bool setWindow);
 
+bool            psphotFullForceSourceStats (pmConfig *config, const pmFPAview *view, const char *filerule, bool setWindow);
+bool            psphotFullForceSourceStatsReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, bool setWindow);
+
 bool            psphotDeblendSatstars (pmConfig *config, const pmFPAview *view, const char *filerule);
 bool            psphotDeblendSatstarsReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe);
@@ -121,4 +125,8 @@
 bool            psphotAddOrSubNoiseReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, bool add);
 bool            psphotAddOrSubNoise_Threaded (psThreadJob *job);
+
+bool            psphotChooseAnalysisOptions (pmConfig *config, const pmFPAview *view, const char *filerule);
+bool            psphotChooseAnalysisOptionsReadout(pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe);
+bool            psphotChooseAnalysisOptionsByObject(pmConfig *config, const pmFPAview *view, const char *filerule, psArray *objects);
 
 bool            psphotExtendedSourceAnalysis (pmConfig *config, const pmFPAview *view, const char *filerule);
@@ -341,4 +349,7 @@
 bool psphotForcedReadout(pmConfig *config, const pmFPAview *view, const char *filerule);
 
+pmConfig *psphotFullForceArguments(int argc, char **argv);
+bool psphotFullForceReadout(pmConfig *config, const pmFPAview *view, const char *filerule);
+
 pmConfig *psphotMinimalArguments(int argc, char **argv);
 bool psphotReadoutMinimal(pmConfig *config, const pmFPAview *view, const char *filerule);
@@ -414,4 +425,13 @@
 } psphotStackOptions;
 
+typedef struct {
+    float fRmajorMin;
+    float fRmajorMax;
+    float fRmajorDel;
+    float fRminorMin;
+    float fRminorMax;
+    float fRminorDel;
+} psphotGalaxyShapeOptions;
+
 /*** psphotStackMatchPSF prototypes ***/
 bool psphotStackMatchPSFs (pmConfig *config, const pmFPAview *view);
@@ -497,4 +517,21 @@
 bool psphotKronIterate_Threaded (psThreadJob *job);
 
+bool psphotKronFlux (pmConfig *config, const pmFPAview *view, const char *filerule);
+bool psphotKronFluxReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, const char *filerule, pmReadout *readout, psArray *sources);
+bool psphotKronFlux_Threaded (psThreadJob *job);
+bool psphotKronFluxSource (pmSource *source, psImageMaskType maskVal);
+
+bool psphotPetroFlux (pmConfig *config, const pmFPAview *view, const char *filerule);
+bool psphotPetroFluxReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, const char * filerule, pmReadout *readout, psArray *sources);
+bool psphotPetroFlux_Threaded (psThreadJob *job);
+bool psphotPetroFluxSource (pmSource *source, psImageMaskType maskVal);
+
+bool psphotGalaxyShape (pmConfig *config, const pmFPAview *view, const char *filerule);
+bool psphotGalaxyShapeReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, const char * filerule, pmReadout *readout, psArray *sources, pmPSF *psf);
+bool psphotGalaxyShape_Threaded (psThreadJob *job);
+bool psphotGalaxyShapeGrid (pmSource *source, pmSourceFitOptions *fitOptions, psphotGalaxyShapeOptions *opt, psImageMaskType maskVal, int psfSize);
+bool psphotGalaxyShapeSource (pmPCMdata *pcm, pmSource *source, psImageMaskType maskVal, int psfSize, bool saveResults);
+psphotGalaxyShapeOptions *psphotGalaxyShapeOptionsAlloc();
+
 bool psphotRadialProfileWings (pmConfig *config, const pmFPAview *view, const char *filerule);
 bool psphotRadialProfileWingsReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources);
Index: trunk/psphot/src/psphotArguments.c
===================================================================
--- trunk/psphot/src/psphotArguments.c	(revision 36374)
+++ trunk/psphot/src/psphotArguments.c	(revision 36375)
@@ -217,4 +217,5 @@
     pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf",      "-psflist");
     pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC",        "-src",      "-srclist");
+    // XXX allow this format? pmConfigFileSetsMD (config->arguments, &argc, argv, "FORCE",      "-force",    "-forcelist");
     pmConfigFileSetsMD (config->arguments, &argc, argv, "EXPNUM",     "-expnum",   "-expnumlist");
 
Index: trunk/psphot/src/psphotBlendFit.c
===================================================================
--- trunk/psphot/src/psphotBlendFit.c	(revision 36374)
+++ trunk/psphot/src/psphotBlendFit.c	(revision 36375)
@@ -274,8 +274,9 @@
 
 # if (PS_TRACE_ON)
-	int TEST_ON = false;
 # define TEST_X 653
 # define TEST_Y 466
-	if ((fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
+# define TESTING 1
+	int TEST_ON = false;
+	if (TESTING && (fabs(source->peak->xf - TEST_X) < 5) && (fabs(source->peak->yf - TEST_Y) < 5)) {
 	    fprintf (stderr, "test object\n");
 	    psTraceSetLevel("psLib.math.psMinimizeLMChi2", 5);
Index: trunk/psphot/src/psphotChooseAnalysisOptions.c
===================================================================
--- trunk/psphot/src/psphotChooseAnalysisOptions.c	(revision 36375)
+++ trunk/psphot/src/psphotChooseAnalysisOptions.c	(revision 36375)
@@ -0,0 +1,556 @@
+# include "psphotInternal.h"
+
+// choose which sources will be processed for the petrosian radii and/or galaxy/trail model fits
+// a source for which we want galaxy models or petrosians gets 
+
+// currently we also skip the following items (regardless of recipe values):
+// TYPE_DEFECT, TYPE_SATURATED, MODE_DEFECT, MODE_SATSTAR, SATSTAR_PROFILE 
+
+// RULES:
+// * global override:
+// ** PSPHOT.EXT.FIT.ALL.SOURCES
+// ** PSPHOT.EXT.FIT.ALL.THRESH (density limit)
+
+// * select by Star/Galaxy?
+// ** PSPHOT.EXT.NSIGMA.LIMIT (sets EXT_LIMIT bit -- star or galaxy)
+// -- PSPHOT.FIT.EXT_LIMIT (bool : ignore or not EXT_LIMIT) 
+
+// * select by S/N?
+// ** EXTENDED_SOURCE_MODELS : SNLIM (per model value)
+
+// * select by Flux?
+// * needs to depend on the filter
+// * define a metadata block 
+// -- EXT.ANALYSIS.MAG.LIMITS (metadata block per filter)
+
+// * select by galactic latitude (global override)?
+// * if (|b| > LIM) do not limit the number of sources by density 
+// -- GLAT_MAX
+
+// * de-select by density (global override)?
+// ??
+
+// for which sources do we want to run the extended source analysis (petrosian and/or galaxy fits)?
+bool psphotChooseAnalysisOptions (pmConfig *config, const pmFPAview *view, const char *filerule)
+{
+    bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Choose Analysis Options ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (i == chisqNum) continue; // skip chisq image
+        if (!psphotChooseAnalysisOptionsReadout (config, view, filerule, i, recipe)) {
+	    psError (PSPHOT_ERR_CONFIG, false, "failed on source size analysis for %s entry %d", filerule, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool GetGalacticCoords (psSphere *ptGal, psSphere *ptSky, psSphereRot *toGal, pmChip *chip, float xPos, float yPos);
+
+// this function use an internal flag to mark sources which have already been measured
+bool psphotChooseAnalysisOptionsReadout(pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe)
+{
+    bool status;
+
+    psTimerStart ("psphot.options");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmChip *chip = pmFPAviewThisChip(view, file->fpa);
+    psAssert (chip, "missing chip?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+        return true;
+    }
+
+    // we do the petrosian analysis for the same sources as the extended source fits IFF
+    // this recipe value is turned on (otherwise we only make a selection based on the mag limits)
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+
+    // Option to enable fitting of all objects with extended model.
+    bool extFitAll = psMetadataLookupBool(&status, recipe, "PSPHOT.EXT.FIT.ALL.SOURCES");
+    assert (status);
+
+    // Fitting everything is fine, but if the source density is high, we probably shouldn't.
+    float extFitAllThresh = psMetadataLookupF32(&status, recipe, "PSPHOT.EXT.FIT.ALL.THRESH");
+    assert (status);
+    
+    // Determine if this readout is above the threshold to ext fit all sources
+    if (extFitAll) {
+      float maskFrac = psMetadataLookupF32(&status,readout->analysis,"READOUT.MASK.FRAC");
+      if (status) maskFrac = 0.0;
+      if (sources->n * (1.0 - maskFrac) > extFitAllThresh) {
+	extFitAll = false;
+      }
+    }
+
+    // use EXT_LIMIT bit to select objects?
+    bool useEXT_LIMIT = psMetadataLookupBool(&status, recipe, "EXT.NSIGMA.LIMIT.USE");
+    assert (status);
+
+    float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
+    assert (status);
+
+    // use GAL_LIMIT to select / skip objects?
+    psSphereRot *toGal = NULL;
+    float GAL_LIMIT = 0.0;
+    bool useGAL_LIMIT = psMetadataLookupBool(&status, recipe, "EXT.FIT.MIN.GAL.LIMIT.USE");
+    assert (status);
+    if (useGAL_LIMIT) {
+	GAL_LIMIT = psMetadataLookupF32(&status, recipe, "EXT.FIT.MIN.GAL.LIMIT");
+	toGal = psSphereRotICRSToGalactic();
+    }
+
+    // use MAG.LIMITS
+    psMetadata *magLimits = psMetadataLookupPtr (&status, recipe, "EXT.ANALYSIS.MAG.LIMITS");
+    if (!status || !magLimits) {   
+        psLogMsg ("psphot", PS_LOG_WARN, "EXT.ANALYSIS.MAG.LIMITS not found in the recipe, will use other criteria.");
+        magLimits = NULL;
+    }
+
+    float extFitFluxLim = NAN;
+
+    if (magLimits) {
+	float extFitMagLimDefault = NAN;
+	float extFitMagLim = NAN;
+
+	// match to the given filter
+	psString filterID = psMetadataLookupStr(&status, file->fpa->concepts, "FPA.FILTERID");
+	psAssert (filterID, "missing FPA.FILTERID?");
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(magLimits, PS_LIST_HEAD, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+            if (item->type != PS_DATA_METADATA) {
+                psAbort ("Invalid type for EXT.ANALYSIS.MAG.LIMITS: %s, not a metadata folder", item->name);
+            }
+            psString thisFilter = psMetadataLookupStr (&status, item->data.md, "FILTER.ID");
+            psAssert(thisFilter, "missing FILTER.ID");
+
+	    // find a matching filter or default to 'any'
+	    if (!strcasecmp (thisFilter, "any")) {
+		psString extFitMagLimStr = psMetadataLookupStr (&status, item->data.md, "MAG.LIMIT");
+		psAssert(extFitMagLimStr, "missing MAG.LIMIT");
+		extFitMagLimDefault = atof (extFitMagLimStr);
+	    }
+
+	    // find a matching filter or default to 'any'
+	    if (!strcasecmp (thisFilter, filterID)) {
+		psString extFitMagLimStr = psMetadataLookupStr (&status, item->data.md, "MAG.LIMIT");
+		psAssert(extFitMagLimStr, "missing MAG.LIMIT");
+		extFitMagLim = atof (extFitMagLimStr);
+		break;
+	    }
+	}
+	if (!isfinite (extFitMagLim)) extFitMagLim = extFitMagLimDefault;
+
+	// now I need to convert the mag limits into instrumental flux limits
+	// I need to get a zero point and exposure time for this image
+	
+	// select the exposure time
+	float exptime = psMetadataLookupF32(&status, file->fpa->concepts, "CELL.EXPOSURE");
+	if (!status) {
+	    exptime = psMetadataLookupF32(&status, file->fpa->concepts, "FPA.EXPOSURE");
+	    psAssert (status, "missing CELL.EXPOSURE and FPA.EXPOSURE?");
+	}
+
+	// select the exposure time
+	float zeropt = psMetadataLookupF32(&status, file->fpa->concepts, "FPA.ZP");
+	psAssert (status, "missing FPA.ZP?");
+
+	extFitFluxLim = exptime * pow (10.0, 0.4*(zeropt - extFitMagLim));
+    }
+
+    pmSourceTmpF clearBits = ~(PM_SOURCE_TMPF_EXT_FIT | PM_SOURCE_TMPF_PETRO);
+    for (psS32 i = 0 ; i < sources->n ; i++) {
+
+        pmSource *source = (pmSource *) sources->data[i];
+
+	// clear the 2 relevant bits
+	source->tmpFlags &= clearBits;
+
+        // skip PSF-like and non-astronomical objects
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+	if (source->mode & PM_SOURCE_MODE_DEFECT) continue;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+
+	// skip saturated stars modeled with a radial profile 
+        if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
+
+        // skip sources without a psf model
+        if (source->modelPSF == NULL) continue;
+
+	// Do the fits if the recipe requests we do extended source fits to everything
+	if (extFitAll) {
+	  source->tmpFlags |= PM_SOURCE_TMPF_EXT_FIT;
+	  if (doPetrosian) source->tmpFlags |= PM_SOURCE_TMPF_PETRO;
+	  continue;
+	}
+
+	if (useEXT_LIMIT) {
+	    if (!(source->mode & PM_SOURCE_MODE_EXT_LIMIT)) {
+		// not extended so skip
+		continue;
+	    }
+	}
+
+	if (useGAL_LIMIT) {
+	    psSphere ptGal, ptSky;
+	    GetGalacticCoords (&ptGal, &ptSky, toGal, chip, source->peak->xf, source->peak->yf);
+	    if (fabs(ptGal.d) < GAL_LIMIT) continue;
+	    // include an exception for low density skycells below the limit?
+	}
+
+	// for petro and extFit, we will either use the mag limits or the S/N
+	if (isfinite(extFitFluxLim)) {
+	    if (source->moments->KronFlux > extFitFluxLim) {
+		source->tmpFlags |= PM_SOURCE_TMPF_EXT_FIT;
+		if (doPetrosian) source->tmpFlags |= PM_SOURCE_TMPF_PETRO;
+	    }
+	} else {
+	    if (source->moments->KronFlux > SN_LIM * source->moments->KronFluxErr) {
+		source->tmpFlags |= PM_SOURCE_TMPF_EXT_FIT;
+		if (doPetrosian) source->tmpFlags |= PM_SOURCE_TMPF_PETRO;
+	    }
+	}
+    }
+
+    psLogMsg ("psphot.options", PS_LOG_WARN, "choose analysis options for %ld sources: %f sec\n", sources->n, psTimerMark ("psphot.options"));
+
+    return true;
+}
+
+// this function use an internal flag to mark sources which have already been measured
+bool psphotChooseAnalysisOptionsByObject(pmConfig *config, const pmFPAview *view, const char *filerule, psArray *objects)
+{
+    bool status;
+
+    psTimerStart ("psphot.options");
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Choose Analysis Options (By Object) ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    // S/N lim to perform radial aperture analysis
+    float SN_LIM_RADIAL = psMetadataLookupF32 (&status, recipe, "RADIAL_APERTURES_SN_LIM");
+
+    // we do the petrosian analysis for the same sources as the extended source fits IFF
+    // this recipe value is turned on (otherwise we only make a selection based on the mag limits)
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+
+    // Option to enable fitting of all objects with extended model.
+    bool extFitAll = psMetadataLookupBool(&status, recipe, "PSPHOT.EXT.FIT.ALL.SOURCES");
+    assert (status);
+
+    // Fitting everything is fine, but if the source density is high, we probably shouldn't.
+    float extFitAllThresh = psMetadataLookupF32(&status, recipe, "PSPHOT.EXT.FIT.ALL.THRESH");
+    assert (status);
+    
+    // use EXT_LIMIT bit to select objects?
+    bool useEXT_LIMIT = psMetadataLookupBool(&status, recipe, "EXT.NSIGMA.LIMIT.USE");
+    assert (status);
+
+    float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
+    assert (status);
+
+    // use GAL_LIMIT to select / skip objects?
+    psSphereRot *toGal = NULL;
+    float GAL_LIMIT = 0.0;
+    bool useGAL_LIMIT = psMetadataLookupBool(&status, recipe, "EXT.FIT.MIN.GAL.LIMIT.USE");
+    assert (status);
+    if (useGAL_LIMIT) {
+	GAL_LIMIT = psMetadataLookupF32(&status, recipe, "EXT.FIT.MIN.GAL.LIMIT");
+	toGal = psSphereRotICRSToGalactic();
+    }
+
+    // use MAG.LIMITS
+    psMetadata *magLimits = psMetadataLookupPtr (&status, recipe, "EXT.ANALYSIS.MAG.LIMITS");
+    if (!status || !magLimits) {   
+        psLogMsg ("psphot", PS_LOG_WARN, "EXT.ANALYSIS.MAG.LIMITS not found in the recipe, will use other criteria.");
+        magLimits = NULL;
+    }
+
+    int num = psphotFileruleCount(config, filerule);
+
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // Find the filter for each of the inputs in fpa concepts
+    psArray *inputFilters = psArrayAlloc(num);
+    psArray *chips = psArrayAlloc(num);
+    psVector *zeropt = psVectorAlloc (inputFilters->n, PS_TYPE_F32);
+    psVector *exptime = psVectorAlloc (inputFilters->n, PS_TYPE_F32);
+
+    // we will not use the chisq image to set the fitting limits
+    if (chisqNum >= 0) {
+      inputFilters->data[chisqNum] = psStringCopy("chisq");
+      chips->data[chisqNum] = NULL;
+      zeropt->data.F32[chisqNum] = NAN;
+      exptime->data.F32[chisqNum] = NAN;
+    }
+
+    // get the needed metadata for the non-chisq images
+    for (int i = 0 ; i < num; i++) {
+      if (i == chisqNum) continue;
+
+      pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i);
+      psAssert (file, "missing file?");
+
+      pmChip *chip = pmFPAviewThisChip(view, file->fpa);
+      psAssert (chip, "missing chip?");
+      chips->data[i] = psMemIncrRefCounter (chip);
+
+      pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+      psAssert (readout, "missing readout?");
+
+      pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+      psAssert (detections, "missing detections?");
+
+      psArray *sources = detections->allSources;
+      psAssert (sources, "missing sources?");
+
+      float maskFrac = psMetadataLookupF32(&status,readout->analysis,"READOUT.MASK.FRAC");
+      if (!status) maskFrac = 0.0;
+      if (sources->n * (1.0 - maskFrac) > extFitAllThresh) {
+	extFitAll = false;
+      }
+
+      // select the filterID for this image
+      char *filterID = psMetadataLookupStr(&status, file->fpa->concepts, "FPA.FILTERID");
+      psAssert (status, "missing FPA.FILTERID?");
+      inputFilters->data[i] = psStringCopy (filterID);
+
+      // select the exposure time for this image
+      exptime->data.F32[i] = psMetadataLookupF32(&status, file->fpa->concepts, "CELL.EXPOSURE");
+      if (!status) {
+	exptime->data.F32[i] = psMetadataLookupF32(&status, file->fpa->concepts, "FPA.EXPOSURE");
+	psAssert (status, "missing CELL.EXPOSURE and FPA.EXPOSURE?");
+      }
+
+      // select the zero point for this image
+      zeropt->data.F32[i] = psMetadataLookupF32(&status, file->fpa->concepts, "FPA.ZP");
+      psAssert (status, "missing FPA.ZP?");
+    }
+
+    // find extFitFluxLim->data.F32[i] for i == image number
+    psVector *extFitFluxLim = NULL;
+    if (magLimits) {
+	extFitFluxLim = psVectorAlloc (inputFilters->n, PS_TYPE_F32);
+	psVectorInit (extFitFluxLim, NAN);
+
+	float extFitMagLimDefault = NAN;
+
+	// match mag limits (flux limits) to the filters
+        psMetadataIterator *iter = psMetadataIteratorAlloc(magLimits, PS_LIST_HEAD, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+            if (item->type != PS_DATA_METADATA) {
+                psAbort ("Invalid type for EXT.ANALYSIS.MAG.LIMITS: %s, not a metadata folder", item->name);
+            }
+            psString thisFilter = psMetadataLookupStr (&status, item->data.md, "FILTER.ID");
+            psAssert(thisFilter, "missing FILTER.ID");
+
+	    // save the default value to assign to unset filters
+	    if (!strcasecmp (thisFilter, "any")) {
+		psString magString = psMetadataLookupStr (&status, item->data.md, "MAG.LIMIT");
+		psAssert(magString, "missing MAG.LIMIT");
+		extFitMagLimDefault = atof (magString);
+		continue;
+	    }
+
+	    // not every entry in the metadata block needs to match to an image in our list
+	    for (int i = 0; i < num; i++) {
+		if (i == chisqNum) continue;
+		if (!strcasecmp (thisFilter, inputFilters->data[i])) {
+		    psString magString = psMetadataLookupStr (&status, item->data.md, "MAG.LIMIT");
+		    psAssert(magString, "missing MAG.LIMIT");
+		    float magvalue = atof (magString);
+		    extFitFluxLim->data.F32[i] = exptime->data.F32[i] * pow (10.0, 0.4*(zeropt->data.F32[i] - magvalue));
+		    break;
+		}
+	    }
+	}
+
+	for (int i = 0; i < num; i++) {
+	    if (i == chisqNum) continue;
+	    if (isfinite(extFitFluxLim->data.F32[i])) continue;
+	    extFitFluxLim->data.F32[i] = exptime->data.F32[i] * pow (10.0, 0.4*(zeropt->data.F32[i] - extFitMagLimDefault));
+	}
+    }
+
+    pmSourceTmpF clearBits = ~(PM_SOURCE_TMPF_EXT_FIT | PM_SOURCE_TMPF_PETRO);
+
+    for (int i = 0; i < objects->n; i++) {
+        pmPhotObj *object = objects->data[i];
+	if (!object) continue;
+	if (!object->sources) continue;
+
+	// we check each source for an object and keep the object if any source is valid
+
+	bool doObjectRadial = false;
+	bool doObjectExtFit = false;
+	for (int j = 0; !doObjectExtFit && !doObjectRadial && (j < object->sources->n); j++) {
+
+	    pmSource *source = object->sources->data[j];
+	    if (!source) continue;
+	    if (!source->peak) continue;
+	    if (source->imageID < 0) continue; // skip sources which come from other images?
+	    if (source->imageID >= num) continue; // skip sources which come from other images?
+
+	    // skip PSF-like and non-astronomical objects
+	    if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	    if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+	    if (source->mode & PM_SOURCE_MODE_DEFECT) continue;
+	    if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+	
+	    // skip saturated stars modeled with a radial profile 
+	    if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
+	
+	    // XXX should I fit all even if one of the detections matches the above?
+
+	    // check on radial aperture analysis (fewer options for now)
+	    if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+		bool doSourceRadial = (source->moments->KronFlux > SN_LIM_RADIAL * source->moments->KronFluxErr);
+		doObjectRadial = doObjectRadial | doSourceRadial;
+	    } else {
+		bool doSourceRadial = (sqrt(source->peak->detValue) > SN_LIM_RADIAL);
+		doObjectRadial = doObjectRadial | doSourceRadial;
+	    }
+
+	    // Do the fits if the recipe requests we do extended source fits to everything
+	    if (extFitAll) {
+		doObjectExtFit = true;
+		continue;
+	    }
+
+	    if (useEXT_LIMIT) {
+		if (!(source->mode & PM_SOURCE_MODE_EXT_LIMIT)) {
+		    continue; // not extended so skip
+		}
+	    }
+
+	    int imageID = source->imageID;
+
+	    if (useGAL_LIMIT) {
+		psSphere ptGal, ptSky;
+		GetGalacticCoords (&ptGal, &ptSky, toGal, chips->data[imageID], source->peak->xf, source->peak->yf);
+		if (fabs(ptGal.d) < GAL_LIMIT) continue;
+		// include an exception for low density skycells below the limit?
+	    }
+
+	    float fluxLim = extFitFluxLim ? extFitFluxLim->data.F32[imageID] : NAN;
+	    // for petro and extFit, we will either use the mag limits or the S/N
+	    if (isfinite(fluxLim)) {
+		if (source->moments->KronFlux > extFitFluxLim->data.F32[imageID]) {
+		    doObjectExtFit = true;
+		}
+	    } else {
+		if (source->moments->KronFlux > SN_LIM * source->moments->KronFluxErr) {
+		    doObjectExtFit = true;
+		}
+	    }
+	}
+
+	for (int j = 0; j < object->sources->n; j++) {
+		
+	    pmSource *source = object->sources->data[j];
+	    if (!source) continue;
+	    if (!source->peak) continue;
+		
+	    // clear the 2 relevant bits
+	    source->tmpFlags &= clearBits;
+		
+	    // skip PSF-like and non-astronomical objects??
+	    if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	    if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+	    if (source->mode & PM_SOURCE_MODE_DEFECT) continue;
+	    if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+		
+	    // skip saturated stars modeled with a radial profile 
+	    if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
+
+            // skip sources without a psf model
+            if (source->modelPSF == NULL) continue;
+		
+	    // Do the fits if the recipe requests we do extended source fits to everything
+	    if (doObjectExtFit) {
+		source->tmpFlags |= PM_SOURCE_TMPF_EXT_FIT;
+		if (doPetrosian) source->tmpFlags |= PM_SOURCE_TMPF_PETRO;
+	    }
+
+	    // Do the fits if the recipe requests we do extended source fits to everything
+	    if (doObjectRadial) {
+		source->tmpFlags |=  PM_SOURCE_TMPF_RADIAL_KEEP;
+		source->tmpFlags &= ~PM_SOURCE_TMPF_RADIAL_SKIP;
+	    } else {
+		source->tmpFlags |=  PM_SOURCE_TMPF_RADIAL_SKIP;
+		source->tmpFlags &= ~PM_SOURCE_TMPF_RADIAL_KEEP;
+	    }
+	}
+    }
+
+    psLogMsg ("psphot.options", PS_LOG_WARN, "choose analysis options for %ld objects: %f sec\n", objects->n, psTimerMark ("psphot.options"));
+
+    return true;
+}
+
+bool GetGalacticCoords (psSphere *ptGal, psSphere *ptSky, psSphereRot *toGal, pmChip *chip, float xPos, float yPos) {
+
+    pmFPA *fpa = chip->parent;
+
+    if (!chip->toFPA) goto escape;
+    if (!fpa->toTPA) goto escape;
+    if (!fpa->toSky) goto escape;
+
+    psPlane ptCH, ptFP, ptTP;
+
+    // calculate the astrometry for the coordinate of interest
+    ptCH.x = xPos;
+    ptCH.y = yPos;
+    psPlaneTransformApply (&ptFP, chip->toFPA, &ptCH);
+    psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+    psDeproject (ptSky, &ptTP, fpa->toSky);
+    psSphereRotApply (ptGal, toGal, ptSky);
+
+    return true;
+
+escape:
+    // no astrometry calibration, give up
+    ptSky->r = NAN;
+    ptSky->d = NAN;
+
+    ptGal->r = NAN;
+    ptGal->d = NAN;
+
+    return false;
+}
Index: trunk/psphot/src/psphotDefineFiles.c
===================================================================
--- trunk/psphot/src/psphotDefineFiles.c	(revision 36374)
+++ trunk/psphot/src/psphotDefineFiles.c	(revision 36375)
@@ -150,4 +150,11 @@
     }
 
+    if (psMetadataLookupPtr(NULL, config->arguments, "FORCE")) {
+        if (!pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT.CFF", "FORCE")) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.INPUT.CFF");
+            return status;
+        }
+    }
+
     if (psMetadataLookupPtr(NULL, config->arguments, "SRCTEXT")) {
 	// XXX cannot use pmFPAfileDefineFromArgs: this is explicitly a FITS-based I/O function
Index: trunk/psphot/src/psphotEllipticalContour.c
===================================================================
--- trunk/psphot/src/psphotEllipticalContour.c	(revision 36374)
+++ trunk/psphot/src/psphotEllipticalContour.c	(revision 36375)
@@ -64,4 +64,5 @@
 	psFree (y);
 	psFree (yErr);
+	source->mode2 |= PM_SOURCE_MODE2_ECONTOUR_FEW_PTS;
 	return false;
     }
Index: trunk/psphot/src/psphotExtendedSourceAnalysis.c
===================================================================
--- trunk/psphot/src/psphotExtendedSourceAnalysis.c	(revision 36374)
+++ trunk/psphot/src/psphotExtendedSourceAnalysis.c	(revision 36375)
@@ -1,5 +1,6 @@
 # include "psphotInternal.h"
-
-// measure the elliptical radial profile and use this to measure the petrosian parameters for the sources
+void psphotRadialProfileShowSkips ();
+
+// measure the petrosian parameters for the sources
 
 // for now, let's store the detections on the readout->analysis for each readout
@@ -15,6 +16,9 @@
     psAssert (recipe, "missing recipe?");
 
-    // perform full non-linear fits / extended source analysis?
-    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANALYSIS")) {
+    bool doPetrosian = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    bool doAnnuli    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+
+    // measure petrosians?
+    if (!doPetrosian && !doAnnuli) {
 	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
 	return true;
@@ -33,4 +37,15 @@
 }
 
+/*** for the moment, this test code : it is not thread safe ***/
+int    Nall = 0;
+int  Nskip1 = 0;
+int  Nskip2 = 0;
+int  Nskip3 = 0;
+int  Nskip4 = 0;
+int  Nskip5 = 0;
+int  Nskip6 = 0;
+
+# define SKIP(VALUE) { VALUE++; continue; }
+
 // aperture-like measurements for extended sources
 bool psphotExtendedSourceAnalysisReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe) {
@@ -110,5 +125,5 @@
 
 // set this to 0 to run without threading
-# if (1)	    
+# if (0)	    
             if (!psThreadJobAddPending(job)) {
                 psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
@@ -164,10 +179,18 @@
     psLogMsg ("psphot", PS_LOG_INFO, "  %d annuli\n", Nannuli);
 
+# if (PS_TRACE_ON)
+    fprintf (stderr, "ext analysis skipped @ 1  : %d\n", Nskip1);
+    fprintf (stderr, "ext analysis skipped @ 2  : %d\n", Nskip2);
+    fprintf (stderr, "ext analysis skipped @ 3  : %d\n", Nskip3);
+    fprintf (stderr, "ext analysis skipped @ 4  : %d\n", Nskip4);
+    fprintf (stderr, "ext analysis skipped @ 5  : %d\n", Nskip5);
+    fprintf (stderr, "ext analysis skipped @ 6  : %d\n", Nskip6);
+#endif
+
+    psphotRadialProfileShowSkips ();
+
     psphotVisualShowResidualImage (readout, false);
 
-    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
-    if (doPetrosian) {
-	psphotVisualShowPetrosians (sources);
-    }
+    psphotVisualShowPetrosians (sources);
 
     return true;
@@ -190,11 +213,5 @@
     float skynoise          = PS_SCALAR_VALUE(job->args->data[4],F32);
 
-    // S/N limit to perform full non-linear fits
-    float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
-
-    // which extended source analyses should we perform?
     bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
-    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
-    bool doPetroStars   = psMetadataLookupBool (&status, recipe, "PETROSIAN_FOR_STARS");
 
     // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
@@ -207,42 +224,15 @@
 	pmSource *source = sources->data[i];
 
-	// if we have checked the source validity on the basis of the object set, then 
-	// we either skip these tests below or we skip the source completely
-	if (source->tmpFlags & PM_SOURCE_TMPF_PETRO_SKIP) continue;
-	if (source->tmpFlags & PM_SOURCE_TMPF_PETRO_KEEP) goto keepSource;
-
-	// skip PSF-like and non-astronomical objects
-	if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
-	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
-	if (source->mode & PM_SOURCE_MODE_DEFECT) continue;
-	if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
-
-	// skip saturated stars modeled with a radial profile 
-        if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
-
-	// optionally allow non-extended objects to get petrosians as well
-	if (!doPetroStars) {
-	    if (!(source->mode & PM_SOURCE_MODE_EXT_LIMIT)) continue;
-	    if (source->type == PM_SOURCE_TYPE_STAR) continue;
-	}
-
-	// limit selection to some SN limit
-	// assert (source->peak); // how can a source not have a peak?
-	// limit selection to some SN limit
-	bool skipSource = false;
-	if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
-	    skipSource = (source->moments->KronFlux < SN_LIM * source->moments->KronFluxErr);
-	} else {
-	    skipSource = (sqrt(source->peak->detValue) < SN_LIM);
-	}
-	if (skipSource) continue;
-
-	// limit selection by analysis region (this automatically apply
-	if (source->peak->x < region->x0) continue;
-	if (source->peak->y < region->y0) continue;
-	if (source->peak->x > region->x1) continue;
-	if (source->peak->y > region->y1) continue;
-
-    keepSource:
+	Nall ++;
+
+	// rules for measuring petrosian parameters for specific objects are set in
+	// psphotChooseAnalysisOptions.c
+	if (!(source->tmpFlags & PM_SOURCE_TMPF_PETRO)) SKIP (Nskip1);
+
+	// limit selection by analysis region (XXX move this into psphotChooseAnalysisOption?)
+	if (source->peak->x < region->x0) SKIP (Nskip2);
+	if (source->peak->y < region->y0) SKIP (Nskip3);
+	if (source->peak->x > region->x1) SKIP (Nskip4);
+	if (source->peak->y > region->y1) SKIP (Nskip5);
 
 	// replace object in image
@@ -259,26 +249,24 @@
 	pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, 1.5*radius);
 
-	// if we request any of these measurements, we require the radial profile
-	if (doPetrosian || doAnnuli) {
-	    if (!psphotRadialProfile (source, recipe, skynoise, maskVal)) {
-		// all measurements below require the radial profile; skip them all
-		// re-subtract the object, leave local sky
-		psTrace ("psphot", 5, "failed to extract radial profile for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
-		pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
-		continue;
-	    }
-	    Nannuli ++;
-	    source->mode |= PM_SOURCE_MODE_RADIAL_FLUX;
-	}
+	// measure the radial profile
+	if (!psphotRadialProfile (source, recipe, skynoise, maskVal)) {
+	  // re-subtract the object, leave local sky
+	  psTrace ("psphot", 5, "failed to extract radial profile for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+	  pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+	  SKIP (Nskip6);
+	}
+
+	Nannuli ++;
+	source->mode |= PM_SOURCE_MODE_RADIAL_FLUX;
 
 	// Petrosian Mags
 	if (doPetrosian) {
-	    if (!psphotPetrosian (source, recipe, skynoise, maskVal)) {
-		psTrace ("psphot", 5, "FAILED petrosian flux & radius for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
-	    } else {
-		psTrace ("psphot", 5, "measured petrosian flux & radius for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
-		Npetro ++;
-		source->mode |= PM_SOURCE_MODE_EXTENDED_STATS;
-	    }
+	  if (!psphotPetrosian (source, recipe, skynoise, maskVal)) {
+	    psTrace ("psphot", 5, "FAILED petrosian flux & radius for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+	  } else {
+	    psTrace ("psphot", 5, "measured petrosian flux & radius for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+	    Npetro ++;
+	    source->mode |= PM_SOURCE_MODE_EXTENDED_STATS;
+	  }
 	}
 
Index: trunk/psphot/src/psphotExtendedSourceFits.c
===================================================================
--- trunk/psphot/src/psphotExtendedSourceFits.c	(revision 36374)
+++ trunk/psphot/src/psphotExtendedSourceFits.c	(revision 36375)
@@ -65,6 +65,4 @@
     psphotVisualShowImage(readout);
 
-    // psphotSaveImage (NULL, readout->image, "test.01.fits");
-
     pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
     psAssert (detections, "missing detections?");
@@ -111,4 +109,9 @@
     if (!status || !isfinite(fitMaxTol) || fitMaxTol <= 0) {
 	fitMaxTol = 1.0;
+    }
+
+    float fitNsigmaConv = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA_CONV"); // number of sigma for the convolution
+    if (!status || !isfinite(fitNsigmaConv) || fitNsigmaConv <= 0) {
+	fitNsigmaConv = 5.0;
     }
 
@@ -137,8 +140,13 @@
     fitOptions->minTol         = fitMinTol;
     fitOptions->maxTol         = fitMaxTol;
+    fitOptions->nsigma         = fitNsigmaConv;
 
     fitOptions->gainFactorMode   = gainFactorMode;
     fitOptions->chisqConvergence = chisqConvergence;
     fitOptions->isInteractive    = isInteractive;
+
+    // use poissonian errors or local-sky errors
+    fitOptions->poissonErrors = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS_POISSON");
+    if (!status) fitOptions->poissonErrors = true;
 
     // maskVal is used to test for rejected pixels, and must include markVal
@@ -285,4 +293,5 @@
             psArrayAdd(job->args, 1, cells->data[j]); // sources
             psArrayAdd(job->args, 1, models);
+
             // Allocate a metadata iterator here because psMetadataIteratorAlloc/Free are not thread safe
             psMetadataIterator *iter = psMetadataIteratorAlloc (models, PS_LIST_HEAD, NULL);
@@ -386,6 +395,4 @@
     psphotSersicModelClassCleanup();
 
-    // psphotSaveImage (NULL, readout->image, "test.02.fits");
-
     psphotVisualShowResidualImage (readout, false);
 
@@ -428,4 +435,12 @@
     // psTraceSetLevel ("psLib.math.psMinimizeLMChi2_Alt", 5);
 
+    pmModelStatus badModel = PM_MODEL_STATUS_NONE;
+    badModel |= PM_MODEL_STATUS_BADARGS;
+    badModel |= PM_MODEL_STATUS_OFFIMAGE;
+    badModel |= PM_MODEL_STATUS_NAN_CHISQ;
+    badModel |= PM_MODEL_SERSIC_PCM_FAIL_GUESS;
+    badModel |= PM_MODEL_SERSIC_PCM_FAIL_GRID;
+    badModel |= PM_MODEL_PCM_FAIL_GUESS;
+
     // choose the sources of interest
     for (int i = 0; i < sources->n; i++) {
@@ -433,23 +448,13 @@
         pmSource *source = sources->data[i];
 
-        // skip PSF-like and non-astronomical objects
-        if (!(source->mode & PM_SOURCE_MODE_EXT_LIMIT)) continue;
-        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
-        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
-
-	// skip saturated stars modeled with a radial profile 
-        if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
-
-        // XXX this should use peak?
+	// rules for measuring petrosian parameters for specific objects are set in
+	// psphotChooseAnalysisOptions.c
+	if (!(source->tmpFlags & PM_SOURCE_TMPF_EXT_FIT)) continue;
+
+	// limit selection by analysis region (XXX move this into psphotChooseAnalysisOption?)
         if (source->peak->x < region->x0) continue;
         if (source->peak->y < region->y0) continue;
         if (source->peak->x > region->x1) continue;
         if (source->peak->y > region->y1) continue;
-
-
-	// XXX for a test, just do the obvious trail
-	// XXX if (source->peak->xf < 1100) continue;
-	// XXX if (source->peak->xf > 1400) continue;
-	// XXX if (source->peak->yf >  245) continue;
 
         // replace object in image
@@ -462,7 +467,4 @@
 	psphotSetRadiusMomentsExact(&fitRadius, &windowRadius, readout, source, markVal); // NOTE : 6 allocs
 
-	// XXX WATCH OUT HERE!!
-	// fitRadius = 30;
-
 	// UPDATE : we have changed the moments calculation.  There is now an iteration within 
 	// psphotKronMasked to determine moments appropriate for a larger object.  The values
@@ -475,10 +477,7 @@
         }
 
+# ifdef TEST_OBJECT
 	bool testObject = false;
-	// testObject |= ((fabs(source->peak->xf -  179) < 5) && (fabs(source->peak->yf - 1138) < 5));
-	// testObject |= ((fabs(source->peak->xf - 5047) < 5) && (fabs(source->peak->yf -  151) < 5));
-	// testObject |= ((fabs(source->peak->xf - 3929) < 5) && (fabs(source->peak->yf - 4109) < 5));
-	// testObject |= ((fabs(source->peak->xf -  915) < 5) && (fabs(source->peak->yf - 5998) < 5));
-	// testObject |= ((fabs(source->peak->xf - 5406) < 5) && (fabs(source->peak->yf -  326) < 5));
+	testObject |= ((fabs(source->peak->xf -  179) < 5) && (fabs(source->peak->yf - 1138) < 5));
 	if (testObject) {
 	    fprintf (stderr, "test object @ %f, %f\n", source->peak->xf, source->peak->yf);
@@ -486,4 +485,5 @@
 	    psTraceSetLevel ("psphot.psphotExtendedSourceFits_Threaded", 5);
 	}
+# endif 
 
         // loop here over the models chosen for each source (exclude by S/N)
@@ -501,15 +501,13 @@
           assert (status);
 
-	  // limit selection to some SN limit
-	  bool skipSource = false;
-	  if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
-	      skipSource = (source->moments->KronFlux < FIT_SN_LIM * source->moments->KronFluxErr);
-	  } else {
-	      skipSource = (sqrt(source->peak->detValue) < FIT_SN_LIM);
-	  }
-          if (skipSource) {
+	  // limit selection to some SN limit for specific models (this value only applies if > EXTENDED_SOURCE_SN_LIM)
+	  if (isfinite(FIT_SN_LIM)) {
+	    if (source->moments->KronFlux < FIT_SN_LIM * source->moments->KronFluxErr) {
 	      Nfaint ++;
 	      continue;
+	    }
 	  }
+
+	  source->mode2 |= PM_SOURCE_MODE2_EXT_FITS_RUN;
 
           // check on the model type
@@ -529,4 +527,5 @@
                   psTrace ("psphot", 5, "failed to fit psf-conv model for object at %f, %f", source->moments->Mx, source->moments->My);
 		  Nfail ++;
+		  source->mode2 |= PM_SOURCE_MODE2_EXT_FITS_FAIL;
                   continue;
               }
@@ -534,5 +533,5 @@
 		       source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq, modelFit->nPix, modelFit->nIter);
               Nconvolve ++;
-              if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
+              if (!(modelFit->flags & badModel)) {
                   NconvolvePass ++;
 		  source->mode |= PM_SOURCE_MODE_EXTENDED_FIT;
@@ -548,9 +547,10 @@
 		      Nfail ++;
 		      doneFits = true;
+		      source->mode2 |= PM_SOURCE_MODE2_EXT_FITS_FAIL;
 		      continue;
 		  }
 		  psTrace ("psphot", 4, "fit plain model for %f, %f : %s chisq = %f (npix: %d, niter: %d)\n", source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq, modelFit->nPix, modelFit->nIter);
 		  Nplain ++;
-		  if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
+		  if (!(modelFit->flags & badModel)) {
 		      NplainPass ++;
 		      source->mode |= PM_SOURCE_MODE_EXTENDED_FIT;
@@ -576,10 +576,10 @@
 			  fprintf (stderr, "update window : %f %f : %f -> %f\n", source->peak->xf, source->peak->yf, fitRadius, 2*fitRadius);
 			  psphotSetWindowTrail (&fitRadius, &windowRadius, readout, source, markVal, fitRadius*2.0);
+			  source->mode2 |= PM_SOURCE_MODE2_EXT_FITS_RETRY;
 		      }
 		  }
 	      }
           }
-	  // XXX really need to do this in a cleaner way:
-	  if (!modelFit) continue;
+	  psAssert (modelFit, "modelFit not set?");
 
           // test for fit quality / result
@@ -590,5 +590,16 @@
         }
 
+	// we are allowed to fit both stars and non-stars here -- if we have fitted
+	// something which we think is a star, we should use that model to subtract the
+	// object from the image.
+        if (source->type == PM_SOURCE_TYPE_STAR) {
+	  // ensure the modelPSF is cached
+	  pmSourceCacheModel (source, maskVal);
+          pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+          continue;
+        }
+
         // evaluate the relative quality of the models, choose one
+	// the PSF model might be the best fit : allow it to succeed
         float minChisq = NAN;
         int minModel = -1;
@@ -596,9 +607,9 @@
             pmModel *model = source->modelFits->data[i];
 
+	    // skip the really bad fits
             if (!(model->flags & PM_MODEL_STATUS_FITTED)) continue;
-
-            if (model->flags & (PM_MODEL_STATUS_BADARGS)) continue;
+            if (model->flags & badModel) continue;
+
             // if (model->flags & (PM_MODEL_STATUS_NONCONVERGE)) continue;
-            if (model->flags & (PM_MODEL_STATUS_OFFIMAGE)) continue;
 
             if ((minModel < 0) || (model->chisq < minChisq)) {
@@ -618,4 +629,5 @@
 	  pmSourceCacheModel (source, maskVal);
 
+# if (PS_TRACE_ON)
 	  pmModel *model = source->modelFits->data[0];
 	  int flags = 0xffffffff;
@@ -623,8 +635,9 @@
 	    flags = model->flags;
 	  }
-
           fprintf (stderr, "failed to fit extended source model to object %d @ %f, %f (%x)\n", source->id, source->moments->Mx, source->moments->My, flags);
+#endif
           pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
 
+	  source->mode2 |= PM_SOURCE_MODE2_EXT_FITS_NONE;
           continue;
         }
@@ -643,9 +656,10 @@
 	if (source->modelEXT->isPCM) {
 	    // fprintf (stderr, "subtract PCM extended source model for object %d @ %f, %f\n", source->id, source->moments->Mx, source->moments->My);
-	    pmPCMCacheModel (source, maskVal, psfSize);
+	    pmPCMCacheModel (source, maskVal, psfSize, fitOptions->nsigma);
 	} else {
 	    // fprintf (stderr, "subtract non-PCM extended source model for object %d @ %f, %f\n", source->id, source->moments->Mx, source->moments->My);
 	    pmSourceCacheModel (source, maskVal);
 	}
+        source->modelEXT->flags |= PM_MODEL_BEST_FIT;
 
         // subtract the best fit from the object, leave local sky
@@ -655,8 +669,10 @@
         psTrace ("psphot", 5, "extended source model for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
 
+# ifdef TEST_OBJECT
 	if (testObject) {
 	    psTraceSetLevel ("psModules.objects.pmPCM_MinimizeChisq", 0);
 	    psTraceSetLevel ("psphot.psphotExtendedSourceFits_Threaded", 0);
 	}
+# endif
     }
 
Index: trunk/psphot/src/psphotFullForce.SourceStats.c
===================================================================
--- trunk/psphot/src/psphotFullForce.SourceStats.c	(revision 36375)
+++ trunk/psphot/src/psphotFullForce.SourceStats.c	(revision 36375)
@@ -0,0 +1,198 @@
+# include "psphotInternal.h"
+
+// construct pixels and measure moments for sources. 
+// NOTE: this function differs from psphotSourceStats in the following ways:
+// 1) detections->allSources are used instead of newSources
+// 2) the sources are used directly (peaks are not assigned to sources here)
+
+// NOTE: this function is meant to be used by psphotFullForce, and the mode of the loaded
+// sources should have (MODE_EXTERNAL) set.  If so, the Mx,My values will not be recalculated
+
+bool psphotFullForceSourceStats (pmConfig *config, const pmFPAview *view, const char *filerule, bool setWindow)
+{
+    bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Source Stats ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // skip the chisq image (optionally?)
+    // int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    // if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        // if (i == chisqNum) continue; // skip chisq image
+        if (!psphotFullForceSourceStatsReadout (config, view, filerule, i, recipe, setWindow)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to find initial detections for %s entry %d", filerule, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool psphotFullForceSourceStatsReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, bool setWindow) {
+
+    bool status = false;
+
+    psTimerStart ("psphot.stats");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    if (psTraceGetLevel("psphot") > 5) {
+	static int pass = 0;
+        char name[64];
+        sprintf (name, "srstats.v%d.fits", pass);
+        psphotSaveImage(NULL, readout->image, name);
+	pass ++;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // determine properties (sky, moments) of initial sources
+    float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    psAssert (status, "missing SKY_OUTER_RADIUS in recipe?");
+
+    // XXX this seems like an arbitrary number...
+    OUTER = PS_MAX(OUTER, 20.0); // XXX Guarantee that we can encompass the max moments radius
+
+    float INNER        = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS"); psAssert (status, "missing SKY_INNER_RADIUS");
+    float MIN_SN       = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN"); psAssert (status, "missing MOMENTS_SN_MIN");
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    psAssert (maskVal, "missing MASK.PSPHOT");
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    psAssert (markVal, "missing MARK.PSPHOT");
+
+    psArray *sources = detections->allSources;
+
+    // generate the array of sources, define the associated pixel
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        psAssert (source->peak, "how can we have a peak-less source?");
+        psAssert (source->moments, "how can we have a moments-less source?");
+
+        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
+        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+    }
+
+    if (setWindow) {
+	// calculate the best gaussian window (keep centroid positions)
+	// NOTE: if sources->mode,mode2 has MODE_EXTERNAL,MODE2_MATCHED, Mx,My are not changed
+        if (!psphotSetMomentsWindow(recipe, readout->analysis, sources, maskVal)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Failed to determine Moments Window!");
+            psFree(detections->newSources);
+            return false;
+        }
+    }
+
+    // if we have measured the window, we will be saving the modified version of these recipe values on readout->analysis
+    float SIGMA = psMetadataLookupF32 (&status, readout->analysis, "MOMENTS_GAUSS_SIGMA");
+    if (!status) {
+        SIGMA = psMetadataLookupF32 (&status, recipe, "MOMENTS_GAUSS_SIGMA");
+    }
+    float RADIUS = psMetadataLookupF32 (&status, readout->analysis, "PSF_MOMENTS_RADIUS");
+    if (!status) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+    float MIN_KRON_RADIUS = psMetadataLookupF32 (&status, readout->analysis, "MOMENTS_MIN_KRON");
+    if (!status) {
+        MIN_KRON_RADIUS = 0.75*SIGMA;
+    }
+
+    // threaded measurement of the source magnitudes
+    int Nfail = 0;
+    int Nmoments = 0;
+    int Nfaint = 0;
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_SOURCE_STATS");
+
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+
+            PS_ARRAY_ADD_SCALAR(job->args, INNER,   PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, MIN_SN,  PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, RADIUS,  PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, SIGMA,   PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, MIN_KRON_RADIUS, PS_TYPE_F32);
+
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal, PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, markVal, PS_TYPE_IMAGE_MASK);
+
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nmoments
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfail
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfaint
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to launch thread job PSPHOT_SOURCE_STATS");
+                psFree(detections->newSources);
+                return false;
+            }
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Failure in thread job PSPHOT_SOURCE_STATS");
+            psFree(detections->newSources);
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            } else {
+                psScalar *scalar = NULL;
+                scalar = job->args->data[8];
+                Nmoments += scalar->data.S32;
+                scalar = job->args->data[9];
+                Nfail += scalar->data.S32;
+                scalar = job->args->data[10];
+                Nfaint += scalar->data.S32;
+            }
+            psFree(job);
+        }
+    }
+
+    psFree (cellGroups);
+
+    psLogMsg ("psphot", PS_LOG_WARN, "%ld sources, %d moments, %d faint, %d failed: %f sec\n", sources->n, Nmoments, Nfaint, Nfail, psTimerMark ("psphot.stats"));
+
+    psphotVisualShowMoments (sources);
+
+    return true;
+}
Index: trunk/psphot/src/psphotFullForce.c
===================================================================
--- trunk/psphot/src/psphotFullForce.c	(revision 36375)
+++ trunk/psphot/src/psphotFullForce.c	(revision 36375)
@@ -0,0 +1,35 @@
+# include "psphotStandAlone.h"
+
+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 = psphotFullForceArguments (argc, argv);
+    assert(config);
+
+    psphotVersionPrint();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotImageLoop (config, PSPHOT_FULL_FORCE)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot 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/psphotFullForceArguments.c
===================================================================
--- trunk/psphot/src/psphotFullForceArguments.c	(revision 36375)
+++ trunk/psphot/src/psphotFullForceArguments.c	(revision 36375)
@@ -0,0 +1,202 @@
+# include "psphotStandAlone.h"
+
+static void usage(const char *program, psMetadata *arg, pmConfig *config, int exitCode);
+static void writeHelpInfo(const char* program, pmConfig* config, FILE* ofile);
+
+pmConfig *psphotFullForceArguments(int argc, char **argv) {
+
+    int N;
+    bool status, status1, status2, status3;
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+      psErrorStackPrint(stderr, "Can't read site configuration");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    PS_ARGUMENTS_GENERIC (psphot, config, argc, argv);
+
+    // 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);
+
+    // Number of threads is handled
+    PS_ARGUMENTS_THREADS( psphot, config, argc, argv );
+
+    if (psArgumentGet(argc, argv, "-help")) writeHelpInfo(argv[0], config, stdout);
+    if (psArgumentGet(argc, argv, "-h"))    writeHelpInfo(argv[0], config, stdout);
+      
+    // visual : interactive display mode
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    //
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",       "-mask",     "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "VARIANCE",   "-variance", "-variancelist");
+
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf",      "-psflist");
+
+    status1 = pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC",     "-src",     "-srclist");
+    status2 = pmConfigFileSetsMD (config->arguments, &argc, argv, "SRCTEXT", "-srctext", "-srctextlist");
+    status3 = pmConfigFileSetsMD (config->arguments, &argc, argv, "FORCE",   "-force",   "-forcelist");
+    
+    if (!(status1 || status2 || status3)) {
+      // XXX require -force version?
+        psError(PSPHOT_ERR_ARGUMENTS, true, "No source list is supplied (use one of -force, -forcelist, -src, -srctext, -srclist, or -srctextlist)");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotFullForceArguments...\n");
+    return (config);
+}
+
+static void writeHelpInfo(const char* program, pmConfig* config, FILE* ofile)
+{
+  fprintf(ofile,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "%s -help\n"
+	  "\n"
+	  "%s -version\n"
+	  "\n"
+	  "where:\n"
+	  "  FileNameList is a text file containing filenames, one per line\n"
+	  "  MaskFileNameList is a text file of mask filenames, one per line\n"
+	  "  VarFileNameList is a text file of variance filenames, one per line\n"
+	  "  OutFileBaseName is the 'root name' for output files\n"
+	  "also required:\n"
+	  "  -src SrcFile1[,SrcFile2,...] or -srclist SrcFileNameList\n"
+	  "     specify sources to be measured (required)\n"
+	  "\n"
+	  "additional options:\n"
+	  "  -psf PsfFile1[,PsfFile2,...] or -psflist PsfFileNameList\n"
+	  "     specify PSF rather than letting %s estimate it\n"
+	  "  -chip nn[,nn,...]\n"
+	  "     select detector chips to process; default is all.\n"
+	  "     Indices correspond to zero-based offset in the FPA metadata table.\n"
+	  "  -region RegionString\n"
+	  "     specify analysis region.  String is of form '[x0:x1,y0:y1]'\n"
+	  "     To use this option you must define a default in psphot.config\n"
+	  "  -visual\n"
+	  "     turns on interactive display mode\n"
+	  "  -dumpconfig CfgFileName\n"
+          "     causes config info to be dumped to the named file.\n"
+	  "  -break NOTHING|BACKMDL|PEAKS|MOMENTS|PSFMODEL|ENSEMBLE|PASS1\n"
+	  "     choose a point at which to exit processing early\n"
+	  "  -threads n\n"
+	  "     set number of parallel threads of execution\n"
+	  "  -F OldFileRule ReplacementFileRule\n"
+	  "     change file naming rule; e.g. '-F PSPHOT.OUTPUT PSPHOT.OUT.CMF.MEF'\n"
+	  "  -D name stringval\n"
+	  "     set a string-valued config parameter\n"
+	  "  -Di name intval\n"
+	  "     set an integer-valued config parameter\n"
+	  "  -Df name fval\n"
+	  "     set a float-valued config parameter\n"
+	  "  -Db name boolval\n"
+	  "     set a boolean-valued config parameter\n"
+	  "  -v, -vv, -vvv\n"
+	  "     set increasing levels of verbosity\n"
+	  "  -logfmt FormatString\n"
+	  "     set format string used for log messages\n"
+	  "  -trace Fac Lvl\n"
+	  "     set tracing for facility Fac to integer Lvl, e.g. '-trace err 10'\n"
+	  "  -trace-levels\n"
+	  "     print current trace levels\n",
+	  program,program,program,program,program);
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_SUCCESS);
+}
+
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  pmConfig *config,      // Configuration
+		  int exitCode
+		  ) 
+{
+  fprintf(stderr,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "also required:\n"
+	  "  -src SrcFile1[,SrcFile2,...] or -srclist SrcFileNameList\n"
+	  "     specify sources to be measured (required)\n"
+	  "\n"
+	  "Try '%s -help' for more options and explanation\n",
+	  program,program,program);
+    if (exitCode != PS_EXIT_SUCCESS)
+      psErrorStackPrint(stderr, "Error reading arguments\n");
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(exitCode);
+}
+
Index: trunk/psphot/src/psphotFullForceImageLoop.c
===================================================================
--- trunk/psphot/src/psphotFullForceImageLoop.c	(revision 36375)
+++ trunk/psphot/src/psphotFullForceImageLoop.c	(revision 36375)
@@ -0,0 +1,153 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) {				\
+	psError(PSPHOT_ERR_DATA, false, MESSAGE);	\
+	psFree (view);					\
+	return false;					\
+    }
+
+bool psphotFullForceImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *load = psMetadataLookupPtr (&status, config->files, "PSPHOT.LOAD");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input 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);
+    pmHDU *lastHDU = NULL;              // Last HDU updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    psImageMaskType maskTest = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+
+    // for psphot, we force data to be read at the chip level
+    while ((chip = pmFPAviewNextChip (view, load->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.LOAD");
+        pmFPAfileActivate (config->files, true, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, true, "PSPHOT.VARIANCE");
+        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.LOAD")) ESCAPE ("Unable to mosaic chip.");
+
+        // Read WCS if easy.
+        // XXX Since we're mosaicking cells, we ignore the case where the WCS is defined for a cell.
+        {
+            pmChip *inChip = pmFPAviewThisChip(view, input->fpa); // Mosaicked chip
+            pmHDU *hduLow = pmHDUGetLowest(input->fpa, inChip, NULL);
+            if (hduLow && !pmAstromReadWCS(input->fpa, inChip, hduLow->header, 1.0)) {
+                psWarning("Unable to read WCS astrometry from header.");
+                psErrorClear();
+                pmHDU *hduHigh = pmHDUGetHighest(input->fpa, inChip, NULL);
+                if (hduHigh && hduHigh != hduLow &&
+                    !pmAstromReadWCS(input->fpa, chip, hduHigh->header, 1.0)) {
+                    psWarning("Unable to read WCS astrometry from primary header.");
+                    psErrorClear();
+                }
+            }
+        }
+
+        // try to load other supporting data (PSF, SRC, etc).
+        // do not re-load the following three files
+        pmFPAfileActivate (config->files, true, NULL);
+        pmFPAfileActivate (config->files, false, "PSPHOT.LOAD");
+        pmFPAfileActivate (config->files, false, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, false, "PSPHOT.VARIANCE");
+        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; }
+
+                // Update the header
+		pmHDU *hdu = pmHDUGetHighest(input->fpa, chip, cell);
+		if (hdu && hdu != lastHDU) {
+		    psphotVersionHeaderFull(hdu->header);
+		    lastHDU = hdu;
+                }
+
+		// if an external mask is supplied, ensure that NAN pixels are also masked
+		if (readout->mask) {
+		    psImageMaskType maskSat = pmConfigMaskGet("SAT", config); // Mask value for saturated pixels
+                    if (!pmReadoutMaskInvalid(readout, maskTest, maskSat)) {
+			psError(psErrorCodeLast(), false, "Unable to mask non-finite pixels.");
+			psFree(view);
+			return false;
+		    }
+		}
+
+                // run the actual photometry analysis on this chip/cell/readout
+                if (!psphotFullForceReadout (config, view, "PSPHOT.INPUT")) {
+                    psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+                    psFree (view);
+                    return false;
+                }
+            }
+
+            // drop all versions of the internal files
+            status = true;
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL.STDEV");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+            if (!status) {
+                psError(PSPHOT_ERR_PROG, false, "trouble dropping internal files");
+                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.VARIANCE
+//
Index: trunk/psphot/src/psphotFullForceReadout.c
===================================================================
--- trunk/psphot/src/psphotFullForceReadout.c	(revision 36375)
+++ trunk/psphot/src/psphotFullForceReadout.c	(revision 36375)
@@ -0,0 +1,110 @@
+# include "psphotInternal.h"
+
+bool psphotFullForceReadout(pmConfig *config, const pmFPAview *view, const char *filerule) {
+
+    // measure the total elapsed time in psphotReadout.  
+    psTimerStart ("psphotReadout");
+
+    // allow objects to be fit with ugly models (central holes, extreme asymmetry, etc)
+    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX);
+
+    // 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;
+    }
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (config, view, filerule)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    PS_ASSERT_PTR_NON_NULL (breakPt, false);
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view, filerule);
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view, filerule)) {
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+    if (!psphotSubtractBackground (config, view, filerule)) {
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+
+    if (!psphotLoadPSF (config, view, filerule)) {
+    	// this only happens if we had a programming error in psphotLoadPSF
+        psError (PSPHOT_ERR_UNKNOWN, false, "error loading psf model");
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+
+    // include externally-supplied sources (loaded onto detections->newSources)
+    psphotLoadExtSources (config, view, filerule);
+
+    // merge the newly selected sources into the existing list (detections->allSources)
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view, filerule);
+
+    // construct sources and measure moments and other basic stats (saved on detections->allSources)
+    // all sources use the auto-scaled window appropriate to a PSF, except for the saturated
+    // stars : these use a larger window (3x the basic window)
+    if (!psphotFullForceSourceStats (config, view, filerule, true)) { // pass 1
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+        return psphotReadoutCleanup (config, view, filerule);
+    }
+
+    // generate a psf model for any readouts which need one
+    // psphotFullForcePSF (config, view, filerule);
+    // XXX I'm not sure we need a different algorithm here or not : we are supplying the
+    // sources; we should mark with a flag bit the ones we actually want to use as PSF
+    // stars (this means we need to supply this info in the load).
+    psphotChoosePSF (config, view, filerule, false);
+
+    // Construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask.  NOTE: only applied to sources without guess models
+    // XXX this currently only generates a PSF model
+    psphotGuessModels (config, view, filerule);
+
+    // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
+    // XXX this will fit non-PSF sources if they are supplied
+    psphotFitSourcesLinear (config, view, filerule, false, false);
+
+    // measure moments
+    // XXX does this set the correct gaussian window (how to do multiple windows?)
+    // we are calling this above...
+    // psphotSourceStats (config, view, filerule, false);
+
+    // measure kron fluxes
+    psphotKronFlux (config, view, filerule);
+
+    // measure petro fluxes
+    psphotPetroFlux (config, view, filerule);
+
+    // measure radial apertures
+    psphotRadialApertures (config, view, filerule, 0);
+
+    // measure galaxy shapes
+    psphotGalaxyShape (config, view, filerule);
+
+    // calculate source magnitudes (psf mag and ap mag)
+    psphotMagnitudes(config, view, filerule);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view, filerule);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (config, view, filerule);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup (config, view, filerule);
+}
Index: trunk/psphot/src/psphotGalaxyShape.c
===================================================================
--- trunk/psphot/src/psphotGalaxyShape.c	(revision 36375)
+++ trunk/psphot/src/psphotGalaxyShape.c	(revision 36375)
@@ -0,0 +1,400 @@
+# include "psphotInternal.h"
+
+bool psphotGalaxyShape (pmConfig *config, const pmFPAview *view, const char *filerule)
+{
+    bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Galaxy Shapes ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    // perform full non-linear fits / extended source analysis?
+    // XXX for this to be true for psphotFullForce??
+    if (!psMetadataLookupBool (&status, recipe, "GALAXY_SHAPES")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "skipping galaxy shape measurements\n");
+	return true;
+    }
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (i == chisqNum) continue; // skip chisq image
+
+        // find the currently selected readout
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i); // File of interest
+        psAssert (file, "missing file?");
+
+        pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+        psAssert (readout, "missing readout?");
+
+        pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+        psAssert (detections, "missing detections?");
+
+        psArray *sources = detections->newSources ? detections->newSources : detections->allSources;
+        psAssert (sources, "missing sources?");
+
+        pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+        // psAssert (psf, "missing psf?");
+
+        if (!psphotGalaxyShapeReadout (config, recipe, view, filerule, readout, sources, psf)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for %s entry %d", filerule, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool psphotGalaxyShapeReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, const char * filerule, pmReadout *readout, psArray *sources, pmPSF *psf) {
+
+    bool status = false;
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping galaxy shapes");
+        return true;
+    }
+
+    psTimerStart ("psphot.galaxy");
+
+    psphotInitRadiusEXT (recipe, readout);
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // bit-mask to mark pixels not used in analysis
+    psphotGalaxyShapeOptions *opt = psphotGalaxyShapeOptionsAlloc();
+    opt->fRmajorMin = psMetadataLookupF32(&status, recipe, "GALAXY_SHAPES_FR_MAJOR_MIN"); if (!status) opt->fRmajorMin = 0.5;
+    opt->fRmajorMax = psMetadataLookupF32(&status, recipe, "GALAXY_SHAPES_FR_MAJOR_MAX"); if (!status) opt->fRmajorMax = 2.0;
+    opt->fRmajorDel = psMetadataLookupF32(&status, recipe, "GALAXY_SHAPES_FR_MAJOR_DEL"); if (!status) opt->fRmajorDel = 0.1;
+    opt->fRminorMin = psMetadataLookupF32(&status, recipe, "GALAXY_SHAPES_FR_MINOR_MIN"); if (!status) opt->fRminorMin = 0.5;
+    opt->fRminorMax = psMetadataLookupF32(&status, recipe, "GALAXY_SHAPES_FR_MINOR_MAX"); if (!status) opt->fRminorMax = 2.0;
+    opt->fRminorDel = psMetadataLookupF32(&status, recipe, "GALAXY_SHAPES_FR_MINOR_DEL"); if (!status) opt->fRminorDel = 0.1;
+
+    // what fraction of the PSF is used? (radius in pixels : 2 -> 5x5 box)
+    // NOTE: this is only used if we are NOT smoothing with a 1D Gaussian
+    int psfSize = psMetadataLookupS32 (&status, recipe, "PCM_BOX_SIZE");
+    assert (status);
+
+    float fitNsigmaConv = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA_CONV"); // number of sigma for the convolutio
+    if (!status || !isfinite(fitNsigmaConv) || fitNsigmaConv <= 0) {
+	fitNsigmaConv = 5.0;
+    }
+
+    // Define source fitting parameters for extended source fits
+    // we are not doing LMM fitting, so most options are irrelevant
+    pmSourceFitOptions *fitOptions = pmSourceFitOptionsAlloc();
+    fitOptions->mode           = PM_SOURCE_FIT_EXT_AND_SKY;
+    fitOptions->covarFactor    = psImageCovarianceFactorForAperture(readout->covariance, 10.0); // Covariance matrix
+    fitOptions->nsigma         = fitNsigmaConv;
+
+    // Poisson or Constant weight for chisq tests?
+    fitOptions->poissonErrors = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS_POISSON");
+    if (!status) fitOptions->poissonErrors = true;
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortByFlux);
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend");
+        return true;
+    }
+
+    // threaded measurement of the source magnitudes
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_GALAXY_SHAPES");
+
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            psArrayAdd(job->args, 1, fitOptions);
+            psArrayAdd(job->args, 1, opt);
+            PS_ARRAY_ADD_SCALAR(job->args, markVal, PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal, PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, psfSize, PS_TYPE_S32);
+
+// set this to 0 to run without threading
+# if (1)
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                return false;
+            }
+# else
+	    if (!psphotGalaxyShape_Threaded(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		// psFree(AnalysisRegion);
+		return false;
+	    }
+	    psFree(job);
+# endif
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
+    }
+    psFree (cellGroups);
+    psFree (fitOptions);
+    psFree (opt);
+
+    psLogMsg ("psphot.galaxy", PS_LOG_WARN, "measure galaxy shapes : %f sec for %ld objects\n", psTimerMark ("psphot.galaxy"), sources->n);
+    return true;
+}
+
+bool psphotGalaxyShape_Threaded (psThreadJob *job) {
+
+    pmReadout *readout      	   = job->args->data[0];
+    psArray *sources        	   = job->args->data[1];
+    pmSourceFitOptions *fitOptions = job->args->data[2];
+    psphotGalaxyShapeOptions *opt  = job->args->data[3];
+    psImageMaskType markVal 	   = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType maskVal 	   = PS_SCALAR_VALUE(job->args->data[5],PS_TYPE_IMAGE_MASK_DATA);
+    int psfSize             	   = PS_SCALAR_VALUE(job->args->data[6],S32);
+
+    float fitRadius;
+    float windowRadius;
+
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+	if (!source->peak) continue; // XXX how can we have a peak-less source?
+
+	// check status of this source's moments
+	if (!source->moments) continue;
+	if (!(source->tmpFlags & PM_SOURCE_TMPF_MOMENTS_MEASURED)) continue;
+	if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) continue;
+
+	// psphotSetRadiusMomentsExact sets the radius based on Mrf
+	if (!isfinite(source->moments->Mrf)) continue;
+
+        // modelFits is allocated if a galaxy fit is requested
+        if (!source->modelFits) continue;
+
+	psphotSetRadiusMomentsExact(&fitRadius, &windowRadius, readout, source, markVal); // NOTE : 6 allocs
+
+	// skip saturated stars modeled with a radial profile 
+	// XXX worry about this at some point..
+	if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
+
+	// replace object in image
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	}
+
+	// this function populates moments->Mrf,GalaxyShape,GalaxyShapeErr
+	// do the following for a set of shapes (Ex,Ey)
+	psphotGalaxyShapeGrid (source, fitOptions, opt, maskVal, psfSize);
+
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    return true;
+}
+
+bool psphotGalaxyShapeGrid (pmSource *source, pmSourceFitOptions *fitOptions, psphotGalaxyShapeOptions *opt, psImageMaskType maskVal, int psfSize) {
+
+    // we use the 0th model to define the initial guess shape
+    pmModel *model = source->modelFits->data[0];
+    if (!model) return false;
+
+    pmModelType modelType = model->type;
+
+    // we are using fitOptions->mode : be sure this makes sense
+    pmPCMdata *pcm = pmPCMinit (source, fitOptions, model, maskVal, psfSize);
+    if (!pcm) return false;
+
+    // we are fitting only PM_PAR_I0; the shape elements are generated from a grid
+    psF32 *PAR = pcm->modelConv->params->data.F32;
+
+    // double check that the guess is carried along...
+
+    // I have some source guess (e0, e1, e2)
+    psEllipseAxes guessAxes = pmPSF_ModelToAxes (PAR, modelType);
+
+    if (!source->galaxyFits) {
+	source->galaxyFits = pmSourceGalaxyFitsAlloc();
+    }
+
+    float fRmajorBest = NAN;
+    float fRminorBest = NAN;
+    float chisqBest = NAN;
+    for (float fRmajor = opt->fRmajorMin; fRmajor < opt->fRmajorMax + 0.5*opt->fRmajorDel; fRmajor += opt->fRmajorDel) {
+	for (float fRminor = opt->fRminorMin; fRminor < opt->fRminorMax + 0.5*opt->fRminorDel; fRminor += opt->fRminorDel) {
+  
+	    psEllipseAxes testAxes = guessAxes;
+	    testAxes.major = guessAxes.major * fRmajor;
+	    testAxes.minor = guessAxes.minor * fRminor;
+	    
+	    pmPSF_AxesToModel (PAR, testAxes, modelType);
+	    
+	    psphotGalaxyShapeSource (pcm, source, maskVal, psfSize, true);
+
+            int i = source->galaxyFits->chisq->n - 1;
+            float thisChisq = source->galaxyFits->chisq->data.F32[i];
+            if (isfinite(thisChisq) && (!isfinite(chisqBest) || thisChisq < chisqBest)) {
+                chisqBest = thisChisq;
+                fRmajorBest = fRmajor;
+                fRminorBest = fRminor;
+            }
+	}
+    }
+
+    if (isfinite(chisqBest)) {
+        // now save the best fitting model as the source's extended model
+        psEllipseAxes testAxes = guessAxes;
+        testAxes.major = guessAxes.major * fRmajorBest;
+        testAxes.minor = guessAxes.minor * fRminorBest;
+        
+        pmPSF_AxesToModel (PAR, testAxes, modelType);
+        
+        psphotGalaxyShapeSource (pcm, source, maskVal, psfSize, false);
+
+        psFree (source->modelEXT);
+
+        source->modelEXT = psMemIncrRefCounter (pcm->modelConv);
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+        source->mode |= PM_SOURCE_MODE_EXTMODEL;
+        source->mode |= PM_SOURCE_MODE_NONLINEAR_FIT;
+
+        // adjust the window so the subtraction covers the faint wings
+        // psphotSetRadiusMoments(&fitRadius, &windowRadius, readout, source, markVal);
+
+        // cache the model flux
+        pmPCMCacheModel (source, maskVal, psfSize, fitOptions->nsigma);
+    }
+
+    psFree (pcm);
+    return true;
+}
+
+// fit the given model to the source and find chisq & normalization
+// XXX is this a single-component model? sersic with a supplied index, Reff, axis ratio, and theta?
+bool psphotGalaxyShapeSource (pmPCMdata *pcm, pmSource *source, psImageMaskType maskVal, int psfSize, bool saveResults) {
+
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+
+    // generate the modelFlux
+    // need to reset here each time since we assign below to calculate the flux
+    // XXX note that this does not add sky to model
+    pmPCMMakeModel (source, pcm->modelConv, pcm->nsigma, maskVal, psfSize);
+    pcm->modelConv->params->data.F32[PM_PAR_I0] = 1.0;
+	
+    int nPix = 0;
+    float YY = 0.0;
+    float YM = 0.0;
+    float MM = 0.0;
+    bool usePoisson = pcm->poissonErrors;
+
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	for (int ix = 0; ix < source->pixels->numCols; ix++) {
+	    // skip masked points
+	    if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix]) {
+		continue;
+	    }
+	    // skip zero-variance points
+	    if (source->variance->data.F32[iy][ix] == 0) {
+		continue;
+	    }
+
+	    // skip nan value points
+	    if (!isfinite(source->pixels->data.F32[iy][ix])) {
+		continue;
+	    }
+
+	    float fy = source->pixels->data.F32[iy][ix];
+	    float fm = source->modelFlux->data.F32[iy][ix];
+	    float wt = (usePoisson) ? 1.0 / source->variance->data.F32[iy][ix] : 1.0;
+
+	    YY += PS_SQR(fy) * wt;
+	    YM += fm * fy * wt;
+	    MM += PS_SQR(fm) * wt;
+	    nPix ++;
+	}
+    }
+
+    float Io = YM / MM;
+    float dIo = sqrt (1.0 / MM);
+    float Chisq = (YY - 2 * Io * YM + Io * Io * MM) / (float) nPix;
+    // NOTE : if !poissonErrors, Chisq is not really the chisq, but is scaled by the flux.
+
+    pcm->modelConv->params->data.F32[PM_PAR_I0] = Io;
+    float flux = pcm->modelConv->modelFlux (pcm->modelConv->params);
+    float dflux = flux * (dIo / Io);
+
+
+    if (saveResults) {
+        psVectorAppend (source->galaxyFits->Flux, flux);
+        psVectorAppend (source->galaxyFits->dFlux, dflux);
+        psVectorAppend (source->galaxyFits->chisq, Chisq);
+        source->galaxyFits->nPix = nPix;
+    }
+
+    return true;
+}
+
+/**** support functions ****/
+
+void psphotGalaxyShapeOptionsFree (psphotGalaxyShapeOptions *opt) {
+    return;
+}
+
+psphotGalaxyShapeOptions *psphotGalaxyShapeOptionsAlloc()
+{
+    psphotGalaxyShapeOptions *opt = (psphotGalaxyShapeOptions *) psAlloc(sizeof(psphotGalaxyShapeOptions));
+    psMemSetDeallocator(opt, (psFreeFunc) psphotGalaxyShapeOptionsFree);
+    
+    opt->fRmajorMin = 0.5;
+    opt->fRmajorMax = 2.0;
+    opt->fRmajorDel = 0.1;
+    opt->fRminorMin = 0.5;
+    opt->fRminorMax = 2.0;
+    opt->fRminorDel = 0.1;
+
+    return opt;
+}
+
Index: trunk/psphot/src/psphotImageLoop.c
===================================================================
--- trunk/psphot/src/psphotImageLoop.c	(revision 36374)
+++ trunk/psphot/src/psphotImageLoop.c	(revision 36375)
@@ -132,4 +132,11 @@
 		    }
 		    break;
+		  case PSPHOT_FULL_FORCE:
+		    if (!psphotFullForceReadout (config, view, "PSPHOT.INPUT")) {
+			psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+			psFree (view);
+			return false;
+		    }
+		    break;
 		  case PSPHOT_MAKE_PSF:
 		    if (!psphotMakePSFReadout (config, view, "PSPHOT.INPUT")) {
Index: trunk/psphot/src/psphotKronFlux.c
===================================================================
--- trunk/psphot/src/psphotKronFlux.c	(revision 36375)
+++ trunk/psphot/src/psphotKronFlux.c	(revision 36375)
@@ -0,0 +1,250 @@
+# include "psphotInternal.h"
+
+bool psphotKronFlux (pmConfig *config, const pmFPAview *view, const char *filerule)
+{
+    bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Kron Fluxes ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (i == chisqNum) continue; // skip chisq image
+
+        // find the currently selected readout
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i); // File of interest
+        psAssert (file, "missing file?");
+
+        pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+        psAssert (readout, "missing readout?");
+
+        pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+        psAssert (detections, "missing detections?");
+
+        psArray *sources = detections->newSources ? detections->newSources : detections->allSources;
+        psAssert (sources, "missing sources?");
+
+        if (!psphotKronFluxReadout (config, recipe, view, filerule, readout, sources)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for %s entry %d", filerule, i);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool psphotKronFluxReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, const char *filerule, pmReadout *readout, psArray *sources) {
+
+    bool status = false;
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping kron fluxes");
+        return true;
+    }
+
+    psTimerStart ("psphot.kron");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortByFlux);
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend");
+        return true;
+    }
+
+    // threaded measurement of the source magnitudes
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_KRON_FLUX");
+
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            PS_ARRAY_ADD_SCALAR(job->args, markVal,            PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,            PS_TYPE_IMAGE_MASK);
+
+// set this to 0 to run without threading
+# if (1)
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                return false;
+            }
+# else
+	    if (!psphotKronFlux_Threaded(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		// psFree(AnalysisRegion);
+		return false;
+	    }
+	    psFree(job);
+# endif
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
+    }
+    psFree (cellGroups);
+
+    psLogMsg ("psphot.kron", PS_LOG_WARN, "measure kron fluxes : %f sec for %ld objects\n", psTimerMark ("psphot.kron"), sources->n);
+    return true;
+}
+
+bool psphotKronFlux_Threaded (psThreadJob *job) {
+
+    pmReadout *readout              = job->args->data[0];
+    psArray *sources                = job->args->data[1];
+    psImageMaskType markVal         = PS_SCALAR_VALUE(job->args->data[2],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[3],PS_TYPE_IMAGE_MASK_DATA);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+	if (!source->peak) continue; // XXX how can we have a peak-less source?
+
+	// check status of this source's moments
+	if (!source->moments) continue;
+	if (!(source->tmpFlags & PM_SOURCE_TMPF_MOMENTS_MEASURED)) continue;
+	if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) continue;
+
+	// XXX where are we storing the supplied kron radius?
+	if (!isfinite(source->moments->Mrf)) continue;
+
+	// skip saturated stars modeled with a radial profile 
+	if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
+
+	// replace object in image
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	}
+
+	// is this right??
+	float windowRadius = 2.5*source->moments->Mrf ;
+
+	// re-allocate image, weight, mask arrays for each peak with box big enough to fit BIG_RADIUS
+	pmSourceRedefinePixels (source, readout, source->peak->x, source->peak->y, windowRadius + 2);
+	psAssert (source->pixels, "WTF?");
+
+	// this function populates moments->Mrf,KronFlux,KronFluxErr
+	psphotKronFluxSource (source, maskVal);
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    return true;
+}
+
+// measure just the flux in the given aperture (this is probably a single common function -- can I use one of the pmSourcePhotometry functions?)
+bool psphotKronFluxSource (pmSource *source, psImageMaskType maskVal) {
+
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+
+    // the peak position is less accurate but less subject to extreme deviations
+    // XXX do this or just use the peak position?
+    float dX = source->moments->Mx - source->peak->xf;
+    float dY = source->moments->My - source->peak->yf;
+    float dR = hypot(dX, dY);
+    float Xo = (dR < 2.0) ? source->moments->Mx : source->peak->xf;
+    float Yo = (dR < 2.0) ? source->moments->My : source->peak->yf;
+
+    // center of mass in subimage.  Note: the calculation below uses pixel index, so we correct
+    // xCM, yCM from pixel coords to pixel index here.
+    psF32 xCM = Xo - 0.5 - source->pixels->col0; // coord of peak in subimage
+    psF32 yCM = Yo - 0.5 - source->pixels->row0; // coord of peak in subimage
+
+    // Calculate the Kron magnitude (make this block optional?)
+    // XXX set the aperture here
+    float radKron  = 2.5*source->moments->Mrf;
+    float radKron2 = radKron*radKron;
+
+    int nKronPix = 0;
+    float Sum = 0.0;
+    float Var = 0.0;
+
+    // set vPix to the source pixels (it may have been set to the
+    // smoothed image above)
+    psF32 **vPix = source->pixels->data.F32;
+    psF32 **vWgt = source->variance->data.F32;
+    psImageMaskType **vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+
+	psF32 yDiff = row - yCM;
+	if (fabs(yDiff) > radKron) continue;
+
+	for (psS32 col = 0; col < source->pixels->numCols ; col++) {
+	    // check mask and value for this pixel
+	    if (vMsk && (vMsk[row][col] & maskVal)) continue;
+	    if (isnan(vPix[row][col])) continue;
+
+	    psF32 xDiff = col - xCM;
+	    if (fabs(xDiff) > radKron) continue;
+
+	    // radKron is just a function of (xDiff, yDiff)
+	    psF32 r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
+	    if (r2 > radKron2) continue;
+
+	    float pDiff = vPix[row][col];
+	    psF32 wDiff = vWgt[row][col];
+
+	    Sum += pDiff;
+	    Var += wDiff;
+	    nKronPix ++;
+	}
+    }
+
+    // return the flux and error to parameters?
+    source->moments->KronFlux    = Sum;
+    source->moments->KronFluxErr = sqrt(Var);
+
+    return true;
+}
Index: trunk/psphot/src/psphotKronIterate.c
===================================================================
--- trunk/psphot/src/psphotKronIterate.c	(revision 36374)
+++ trunk/psphot/src/psphotKronIterate.c	(revision 36375)
@@ -166,5 +166,7 @@
     }
 
+# if (PS_TRACE_ON)
     fprintf (stderr, "--- starting KRON ---\n");
+#endif
 
     // We measure the Kron Radius on a smoothed copy of the readout image
Index: trunk/psphot/src/psphotLoadPSF.c
===================================================================
--- trunk/psphot/src/psphotLoadPSF.c	(revision 36374)
+++ trunk/psphot/src/psphotLoadPSF.c	(revision 36375)
@@ -1,3 +1,20 @@
 # include "psphotInternal.h"
+
+// PSPHOT.PSF.LOAD vs input file -- see note at top
+bool psphotLoadPSF (pmConfig *config, const pmFPAview *view, const char *filerule) {
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+        // Generate the mask and weight images, including the user-defined analysis region of interest
+        if (!psphotLoadPSFReadout (config, view, filerule, "PSPHOT.PSF.LOAD", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to load PSF model for PSPHOT.PSF.LOAD entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
 
 // NOTE : pmPSF_IO.c functions must load the psf model onto the chip->analysis metadata because
@@ -58,19 +75,2 @@
     return true;
 }
-
-// PSPHOT.PSF.LOAD vs input file -- see note at top
-bool psphotLoadPSF (pmConfig *config, const pmFPAview *view, const char *filerule) {
-
-    int num = psphotFileruleCount(config, filerule);
-
-    // loop over the available readouts
-    for (int i = 0; i < num; i++) {
-
-        // Generate the mask and weight images, including the user-defined analysis region of interest
-        if (!psphotLoadPSFReadout (config, view, filerule, "PSPHOT.PSF.LOAD", i)) {
-            psError (PSPHOT_ERR_CONFIG, false, "failed to load PSF model for PSPHOT.PSF.LOAD entry %d", i);
-            return false;
-        }
-    }
-    return true;
-}
Index: trunk/psphot/src/psphotMergeSources.c
===================================================================
--- trunk/psphot/src/psphotMergeSources.c	(revision 36374)
+++ trunk/psphot/src/psphotMergeSources.c	(revision 36375)
@@ -39,12 +39,4 @@
     psAssert (newSources, "missing sources?");
 
-    // XXX TEST:
-    if (detections->allSources) {
-        psphotMaskCosmicRayFootprintCheck(detections->allSources);
-    }
-    if (detections->newSources) {
-        psphotMaskCosmicRayFootprintCheck(detections->newSources);
-    }
-
     if (!detections->allSources) {
         detections->allSources = psArrayAllocEmpty(newSources->n);
@@ -65,4 +57,7 @@
 // Merge the externally supplied sources with the existing sources.  Mark them as having mode
 // PM_SOURCE_MODE_EXTERNAL.
+
+// XXX this function needs to be updated slightly for psphotFullForce:
+// * load the additional parameters to guide the new concepts
 
 // XXX This function needs to be updated to loop over set of input files.  At the moment, we
@@ -73,4 +68,5 @@
     bool status;
     pmDetections *extCMF = NULL;
+    pmDetections *extCFF = NULL;
     psArray *extSourcesTXT = NULL;
     int index = 0;
@@ -102,5 +98,5 @@
     {
         pmReadout *readoutCMF = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CMF");
-        if (!readoutCMF) goto loadTXT;
+        if (!readoutCMF) goto loadCFF;
 
         extCMF = psMetadataLookupPtr (NULL, readoutCMF->analysis, "PSPHOT.DETECTIONS");
@@ -125,4 +121,37 @@
     }
 
+loadCFF:
+    // load data from input CFF file:
+    {
+        pmReadout *readoutCFF = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CFF");
+        if (!readoutCFF) goto loadTXT;
+
+        extCFF = psMetadataLookupPtr (NULL, readoutCFF->analysis, "PSPHOT.DETECTIONS");
+        if (extCFF) {
+            psF32 exptime = psMetadataLookupF32(NULL, readout->parent->concepts, "CELL.EXPOSURE");
+            for (int i = 0; i < extCFF->allSources->n; i++) {
+                pmSource *source = extCFF->allSources->data[i];
+
+		// setting this bit not only tracks the inputs, it makes pmSourceMoments
+		// keep the Mx,My values for the centroid.  
+                source->mode |= PM_SOURCE_MODE_EXTERNAL;
+
+		// source->peak->detValue,rawFlux,smoothFlux all set to input flux value which is scaled
+                // to 1 second exposure time. Scale to this image's exposure.
+                source->peak->rawFlux    *= exptime;
+                source->peak->smoothFlux *= exptime;
+                source->peak->detValue   *= exptime;
+		// source->peak->xf,yf, moments->Mx,My all set to input position
+
+                // drop the loaded source modelPSF
+                psFree (source->modelPSF);
+                source->modelPSF = NULL;
+		source->imageID = index;
+
+                psArrayAdd (detections->newSources, 100, source);
+            }
+        }
+    }
+
 loadTXT:
 
@@ -157,5 +186,5 @@
     psFree (detections);
 
-    if (!extCMF && !extSourcesTXT) {
+    if (!(extCMF || extCFF || extSourcesTXT)) {
         psLogMsg ("psphot", 3, "no external sources for this readout");
         return true;
@@ -163,8 +192,9 @@
 
     int nCMF = extCMF        ? extCMF->allSources->n        : 0;
-    int nTXT = extSourcesTXT ? extSourcesTXT->n : 0;
-
-    psLogMsg ("psphot", 3, "%d external sources (%d cmf, %d text) merged to yield %ld total sources",
-              nCMF + nTXT, nCMF, nTXT, sources->n);
+    int nCFF = extCFF        ? extCFF->allSources->n        : 0;
+    int nTXT = extSourcesTXT ? extSourcesTXT->n             : 0;
+
+    psLogMsg ("psphot", 3, "%d external sources (%d cmf, %d cff, %d text) merged to yield %ld total sources",
+              nCMF + nCFF + nTXT, nCMF, nCFF, nTXT, sources->n);
     return true;
 }
Index: trunk/psphot/src/psphotModelTestReadout.c
===================================================================
--- trunk/psphot/src/psphotModelTestReadout.c	(revision 36374)
+++ trunk/psphot/src/psphotModelTestReadout.c	(revision 36375)
@@ -125,5 +125,5 @@
 
 	    // get the source moments
-	    status = pmSourceMoments (source, radius, 0.25*radius, 0.0, MIN_KRON_RADIUS, maskVal);
+	  status = pmSourceMoments (source, radius, 0.25*radius, 0.0, MIN_KRON_RADIUS, maskVal);
 	    if (!status) psAbort("psSourceMoments error");
 
Index: trunk/psphot/src/psphotOldCode.c
===================================================================
--- trunk/psphot/src/psphotOldCode.c	(revision 36375)
+++ trunk/psphot/src/psphotOldCode.c	(revision 36375)
@@ -0,0 +1,350 @@
+
+/***************
+
+  In the process of developing psphot, I have written a bunch of code that has been used
+  for some tests or for a period, but which we no longer think is appropriate.  I am
+  trying to clean up the kruft of old psphot bits and will but various such functions in
+  this file if they are worth keeping
+
+****************/
+
+bool psphotMaskCosmicRayFootprintCheck (psArray *sources);
+
+
+// maybe move this into psModules, pmFootprints?
+bool psphotMaskCosmicRayFootprintCheck (psArray *sources) {
+#ifdef CHECK_FOOTPRINTS
+    // This gets really expensive for complex images
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmPeak *peak = source->peak;
+        pmFootprint *footprint = peak->footprint;
+        if (!footprint) continue;
+        for (int j = 0; j < footprint->spans->n; j++) {
+            pmSpan *sp = footprint->spans->data[j];
+            psAssert (sp, "missing span");
+        }
+    }
+#endif
+    return true;
+}
+
+// mark the isophotal boundary
+bool psphotMaskCosmicRayIsophot (pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
+
+    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+    pmPeak *peak = source->peak;
+    psAssert (peak, "NULL peak");
+
+    psImage *mask   = source->maskView;
+    psImage *pixels = source->pixels;
+    psImage *variance = source->variance;
+
+    // XXX This should be a recipe variable
+# define SN_LIMIT 5.0
+
+    int xo = peak->x - pixels->col0;
+    int yo = peak->y - pixels->row0;
+
+    // mark the pixels in this row to the left, then the right
+    for (int ix = xo; ix >= 0; ix--) {
+        float SN = pixels->data.F32[yo][ix] / sqrt(variance->data.F32[yo][ix]);
+        if (SN > SN_LIMIT) {
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[yo][ix] |= crMask;
+        }
+    }
+    for (int ix = xo + 1; ix < pixels->numCols; ix++) {
+        float SN = pixels->data.F32[yo][ix] / sqrt(variance->data.F32[yo][ix]);
+        if (SN > SN_LIMIT) {
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[yo][ix] |= crMask;
+        }
+    }
+
+    // for each of the neighboring rows, mark the high pixels if they have a marked neighbor
+    // first go up:
+    for (int iy = PS_MIN(yo, mask->numRows-2); iy >= 0; iy--) {
+        // mark the pixels in this row to the left, then the right
+        for (int ix = 0; ix < pixels->numCols; ix++) {
+            float SN = pixels->data.F32[iy][ix] / sqrt(variance->data.F32[iy][ix]);
+            if (SN < SN_LIMIT) continue;
+
+            bool valid = false;
+            valid |= (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix] & crMask);
+            valid |= (ix > 0) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix-1] & crMask) : 0;
+            valid |= (ix <= mask->numCols) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix+1] & crMask) : 0;
+
+            if (!valid) continue;
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
+        }
+    }
+    // next go down:
+    for (int iy = PS_MIN(yo+1, mask->numRows-1); iy < pixels->numRows; iy++) {
+        // mark the pixels in this row to the left, then the right
+        for (int ix = 0; ix < pixels->numCols; ix++) {
+            float SN = pixels->data.F32[iy][ix] / sqrt(variance->data.F32[iy][ix]);
+            if (SN < SN_LIMIT) continue;
+
+            bool valid = false;
+            valid |= (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix] & crMask);
+            valid |= (ix > 0) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix-1] & crMask) : 0;
+            valid |= (ix <= mask->numCols) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix+1] & crMask) : 0;
+
+            if (!valid) continue;
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
+        }
+    }
+    return true;
+}
+
+// This attempt to mask the cosmic rays used the isophotal boundary
+bool psphotMaskCosmicRay_V1 (psImage *mask, pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
+
+    // replace the source flux
+    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+    // flag this as a CR
+    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+    pmPeak *peak = source->peak;
+    psAssert (peak, "NULL peak");
+
+    // grab the matching footprint
+    pmFootprint *footprint = peak->footprint;
+    if (!footprint) {
+      psTrace("psphot.czw",2,"Using isophot CR mask code.");
+
+        // if we have not footprint, use the old code to mask by isophot
+        psphotMaskCosmicRayIsophot (source, maskVal, crMask);
+        return true;
+    }
+
+    if (!footprint->spans) {
+      psTrace("psphot.czw",2,"Using isophot CR mask code.");
+
+        // if we have no footprint, use the old code to mask by isophot
+        psphotMaskCosmicRayIsophot (source, maskVal, crMask);
+        return true;
+    }
+    psphotMaskCosmicRayIsophot (source, maskVal, crMask);
+    // mask all of the pixels covered by the spans of the footprint
+    for (int j = 1; j < footprint->spans->n; j++) {
+        pmSpan *span1 = footprint->spans->data[j];
+
+        int iy = span1->y;
+        int xs = span1->x0;
+        int xe = span1->x1;
+
+        for (int ix = xs; ix < xe; ix++) {
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
+        }
+    }
+    return true;
+}
+
+// given the PSF ellipse parameters, navigate around the 1sigma contour, return the total
+// deviation in sigmas.  This is measured on the residual image - should we ignore negative
+// deviations?  NOTE: This function was an early attempt to classify extended objects, and is
+// no longer used by psphot.
+float psphotModelContour(const psImage *image, const psImage *variance, const psImage *mask,
+                         psImageMaskType maskVal, const pmModel *model, float Ro)
+{
+    psF32 *PAR = model->params->data.F32; // Model parameters
+    float sxx = PAR[PM_PAR_SXX], sxy = PAR[PM_PAR_SXY], syy = PAR[PM_PAR_SYY]; // Ellipse parameters
+
+    // We treat the contour as an ellipse:
+    // Ro = (x / SXX)^2 + (y / SYY)^2 + x y SXY
+    // y^2 (1/SYY^2) + y (x SXY) + (x / SXX)^2 - Ro = 0;
+    // This is a quadratic, Ay^2 + By + C with A = 1/SYY^2, B = x*SXY, C = (x / SXX)^2 - Ro
+    // The solution is y = [-B +/- sqrt (B^2 - 4 A C)] / [2 A], so:
+    // y = [-(x SXY) +/- sqrt ((x SXY)^2 - 4 (1/SYY^2) ((x/SXX)^2 - Ro))] * [SYY^2 / 2]
+
+    // min/max value of x is where B^2 - 4AC = 0; solve this for x
+    float Q = Ro * PS_SQR(sxx) / (1.0 - PS_SQR(sxx * syy * sxy) / 4.0);
+    if (Q < 0.0) {
+        // ellipse is imaginary
+        return NAN;
+    }
+
+    int radius = sqrtf(Q) + 0.5;        // Radius of ellipse
+    int nPts = 0;                       // Number of points in ellipse
+    float nSigma = 0.0;                 //
+
+    for (int x = -radius; x <= radius; x++) {
+        // Polynomial coefficients
+        // XXX Should we be using the centre of the pixel as x or x+0.5?
+        float A = PS_SQR (1.0 / syy);
+        float B = x * sxy;
+        float C = PS_SQR (x / sxx) - Ro;
+        float T = PS_SQR(B) - 4*A*C;
+        if (T < 0.0) {
+            continue;
+        }
+
+        // y position in source frame
+        float yP = (-B + sqrt (T)) / (2.0 * A);
+        float yM = (-B - sqrt (T)) / (2.0 * A);
+
+        // Get the closest pixel positions (image frame)
+        int xPix  = x  + PAR[PM_PAR_XPOS] - image->col0 + 0.5;
+        int yPixM = yM + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
+        int yPixP = yP + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
+
+        if (xPix < 0 || xPix >= image->numCols) {
+            continue;
+        }
+
+        if (yPixM >= 0 && yPixM < image->numRows &&
+            !(mask && (mask->data.PS_TYPE_IMAGE_MASK_DATA[yPixM][xPix] & maskVal))) {
+            float dSigma = image->data.F32[yPixM][xPix] / sqrtf(variance->data.F32[yPixM][xPix]);
+            nSigma += dSigma;
+            nPts++;
+        }
+
+        if (yPixM == yPixP) {
+            continue;
+        }
+
+        if (yPixP >= 0 && yPixP < image->numRows &&
+            !(mask && (mask->data.PS_TYPE_IMAGE_MASK_DATA[yPixP][xPix] & maskVal))) {
+            float dSigma = image->data.F32[yPixP][xPix] / sqrtf(variance->data.F32[yPixP][xPix]);
+            nSigma += dSigma;
+            nPts++;
+        }
+    }
+    nSigma /= nPts;
+    return nSigma;
+}
+
+// this was an old attempt to identify cosmic rays based on the peak curvature
+bool psphotSourcePeakCurvature (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options) {
+
+    // classify the sources based on the CR test (place this in a function?)
+    // XXX use an internal flag to mark sources which have already been measured
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip source if it was already measured
+        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
+            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
+            continue;
+        }
+
+        // source must have been subtracted
+        if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) {
+            source->mode |= PM_SOURCE_MODE_SIZE_SKIPPED;
+            psTrace("psphot", 7, "Not calculating source size since source is not subtracted\n");
+            continue;
+        }
+
+        psF32 **resid  = source->pixels->data.F32;
+        psF32 **variance = source->variance->data.F32;
+        psImageMaskType **mask = source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
+
+        // Integer position of peak
+        int xPeak = source->peak->xf - source->pixels->col0 + 0.5;
+        int yPeak = source->peak->yf - source->pixels->row0 + 0.5;
+
+        // Skip sources which are too close to a boundary.  These are mostly caught as DEFECT
+        if (xPeak < 1 || xPeak > source->pixels->numCols - 2 ||
+            yPeak < 1 || yPeak > source->pixels->numRows - 2) {
+            psTrace("psphot", 7, "Not calculating crNsigma due to edge\n");
+            continue;
+        }
+
+        // Skip sources with masked pixels.  These are mostly caught as DEFECT
+        bool keep = true;
+        for (int iy = -1; (iy <= +1) && keep; iy++) {
+            for (int ix = -1; (ix <= +1) && keep; ix++) {
+                if (mask[yPeak+iy][xPeak+ix] & options->maskVal) {
+                    keep = false;
+                }
+            }
+        }
+        if (!keep) {
+            psTrace("psphot", 7, "Not calculating crNsigma due to masked pixels\n");
+            continue;
+        }
+
+        // Compare the central pixel with those on either side, for the four possible lines through it.
+
+        // Soften variances (add systematic error)
+        float softening = options->soft * PS_SQR(source->peak->rawFlux); // Softening for variances
+
+        // Across the middle: y = 0
+        float cX = 2*resid[yPeak][xPeak]   - resid[yPeak+0][xPeak-1]  - resid[yPeak+0][xPeak+1];
+        float dcX = 4*variance[yPeak][xPeak] + variance[yPeak+0][xPeak-1] + variance[yPeak+0][xPeak+1];
+        float nX = cX / sqrtf(dcX + softening);
+
+        // Up the centre: x = 0
+        float cY = 2*resid[yPeak][xPeak]   - resid[yPeak-1][xPeak+0]  - resid[yPeak+1][xPeak+0];
+        float dcY = 4*variance[yPeak][xPeak] + variance[yPeak-1][xPeak+0] + variance[yPeak+1][xPeak+0];
+        float nY = cY / sqrtf(dcY + softening);
+
+        // Diagonal: x = y
+        float cL = 2*resid[yPeak][xPeak]   - resid[yPeak-1][xPeak-1]  - resid[yPeak+1][xPeak+1];
+        float dcL = 4*variance[yPeak][xPeak] + variance[yPeak-1][xPeak-1] + variance[yPeak+1][xPeak+1];
+        float nL = cL / sqrtf(dcL + softening);
+
+        // Diagonal: x = - y
+        float cR = 2*resid[yPeak][xPeak]   - resid[yPeak+1][xPeak-1]  - resid[yPeak-1][xPeak+1];
+        float dcR = 4*variance[yPeak][xPeak] + variance[yPeak+1][xPeak-1] + variance[yPeak-1][xPeak+1];
+        float nR = cR / sqrtf(dcR + softening);
+
+        // P(chisq > chisq_obs; Ndof) = gamma_Q (Ndof/2, chisq/2)
+        // Ndof = 4 ? (four measurements, no free parameters)
+        // XXX this value is going to be biased low because of systematic errors.
+        // we need to calibrate it somehow
+        // source->psfProb = gsl_sf_gamma_inc_Q (2, 0.5*chisq);
+
+        // not strictly accurate: overcounts the chisq contribution from the center pixel (by
+        // factor of 4); also biases a bit low if any pixels are masked
+        // XXX I am not sure I want to keep this value...
+        source->psfChisq = PS_SQR(nX) + PS_SQR(nY) + PS_SQR(nL) + PS_SQR(nR);
+
+        float fCR = 0.0;
+        int nCR = 0;
+        if (nX > 0.0) {
+            fCR += nX;
+            nCR ++;
+        }
+        if (nY > 0.0) {
+            fCR += nY;
+            nCR ++;
+        }
+        if (nL > 0.0) {
+            fCR += nL;
+            nCR ++;
+        }
+        if (nR > 0.0) {
+            fCR += nR;
+            nCR ++;
+        }
+        source->crNsigma  = (nCR > 0)  ? fCR / nCR : 0.0;
+        source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+
+        if (!isfinite(source->crNsigma)) {
+            continue;
+        }
+
+        // this source is thought to be a cosmic ray.  flag the detection and mask the pixels
+        if (source->crNsigma > options->nSigmaCR) {
+            source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+            // XXX still testing... : psphotMaskCosmicRay (readout->mask, source, maskVal, crMask);
+            // XXX acting strange... psphotMaskCosmicRay_Old (source, maskVal, crMask);
+        }
+    }
+
+    // now that we have masked pixels associated with CRs, we can grow the mask
+    if (options->grow > 0) {
+        bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading for psImageConvolveMask
+        psImage *newMask = psImageConvolveMask(NULL, readout->mask, options->crMask, options->crMask, -options->grow, options->grow, -options->grow, options->grow);
+        psImageConvolveSetThreads(oldThreads);
+        if (!newMask) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to grow CR mask");
+            return false;
+        }
+        psFree(readout->mask);
+        readout->mask = newMask;
+    }
+    return true;
+}
+
Index: trunk/psphot/src/psphotPetroFlux.c
===================================================================
--- trunk/psphot/src/psphotPetroFlux.c	(revision 36375)
+++ trunk/psphot/src/psphotPetroFlux.c	(revision 36375)
@@ -0,0 +1,259 @@
+# include "psphotInternal.h"
+
+# define PETROSIAN_RADII 2.0
+
+bool psphotPetroFlux (pmConfig *config, const pmFPAview *view, const char *filerule)
+{
+    bool status = true;
+
+    fprintf (stdout, "\n");
+    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Petro Fluxes ---");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psphotFileruleCount(config, filerule);
+
+    // skip the chisq image (optionally?)
+    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
+    if (!status) chisqNum = -1;
+
+    psMetadataAddBool (recipe, PS_LIST_TAIL, "EXTENDED_SOURCE_ANALYSIS", PS_META_REPLACE, "we measured this, save to disk", true);
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (i == chisqNum) continue; // skip chisq image
+
+        // find the currently selected readout
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, i); // File of interest
+        psAssert (file, "missing file?");
+
+        pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+        psAssert (readout, "missing readout?");
+
+        pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+        psAssert (detections, "missing detections?");
+
+        psArray *sources = detections->newSources ? detections->newSources : detections->allSources;
+        psAssert (sources, "missing sources?");
+
+        if (!psphotPetroFluxReadout (config, recipe, view, filerule, readout, sources)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for %s entry %d", filerule, i);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool psphotPetroFluxReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, const char * filerule, pmReadout *readout, psArray *sources) {
+
+    bool status = false;
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping petrosian fluxes");
+        return true;
+    }
+
+    psTimerStart ("psphot.petro");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortByFlux);
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend");
+        return true;
+    }
+
+    // threaded measurement of the source magnitudes
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_PETRO_FLUX");
+
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            PS_ARRAY_ADD_SCALAR(job->args, markVal,            PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,            PS_TYPE_IMAGE_MASK);
+
+// set this to 0 to run without threading
+# if (1)
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                return false;
+            }
+# else
+	    if (!psphotPetroFlux_Threaded(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		// psFree(AnalysisRegion);
+		return false;
+	    }
+	    psFree(job);
+# endif
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
+    }
+    psFree (cellGroups);
+
+    psLogMsg ("psphot.petro", PS_LOG_WARN, "measure petro fluxes : %f sec for %ld objects\n", psTimerMark ("psphot.petro"), sources->n);
+    return true;
+}
+
+bool psphotPetroFlux_Threaded (psThreadJob *job) {
+
+    pmReadout *readout              = job->args->data[0];
+    psArray *sources                = job->args->data[1];
+    psImageMaskType markVal         = PS_SCALAR_VALUE(job->args->data[2],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[3],PS_TYPE_IMAGE_MASK_DATA);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+	if (!source->peak) continue; // XXX how can we have a peak-less source?
+	if (!source->extpars) continue; // if this is not set, we did not have a valid petRadius
+
+	// check status of this source's moments
+	if (!source->moments) continue;
+	if (!(source->tmpFlags & PM_SOURCE_TMPF_MOMENTS_MEASURED)) continue;
+	if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) continue;
+
+	// XXX where are we storing the supplied petro radius?
+	if (!isfinite(source->moments->Mrf)) continue;
+
+	// skip saturated stars modeled with a radial profile 
+	if (source->mode2 & PM_SOURCE_MODE2_SATSTAR_PROFILE) continue;
+
+	// replace object in image
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	}
+
+	// is the right??  XXX need the correct location for Rpet
+	float windowRadius = 2.0*source->moments->Mrf ;
+
+	// re-allocate image, weight, mask arrays for each peak with box big enough to fit BIG_RADIUS
+	pmSourceRedefinePixels (source, readout, source->peak->x, source->peak->y, windowRadius + 2);
+	psAssert (source->pixels, "WTF?");
+
+	// this function populates moments->Mrf,PetroFlux,PetroFluxErr
+	psphotPetroFluxSource (source, maskVal);
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    return true;
+}
+
+// measure just the flux in the given aperture (this is probably a single common function -- can I use one of the pmSourcePhotometry functions?)
+bool psphotPetroFluxSource (pmSource *source, psImageMaskType maskVal) {
+
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->variance, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->extpars, false);
+
+    // the peak position is less accurate but less subject to extreme deviations
+    float dX = source->moments->Mx - source->peak->xf;
+    float dY = source->moments->My - source->peak->yf;
+    float dR = hypot(dX, dY);
+    float Xo = (dR < 2.0) ? source->moments->Mx : source->peak->xf;
+    float Yo = (dR < 2.0) ? source->moments->My : source->peak->yf;
+
+    // center of mass in subimage.  Note: the calculation below uses pixel index, so we correct
+    // xCM, yCM from pixel coords to pixel index here.
+    psF32 xCM = Xo - 0.5 - source->pixels->col0; // coord of peak in subimage
+    psF32 yCM = Yo - 0.5 - source->pixels->row0; // coord of peak in subimage
+
+    // Calculate the Petro magnitude (make this block optional?)
+    // XXX set the aperture here
+    float radPetro  = PETROSIAN_RADII*source->extpars->petrosianRadius;
+    float radPetro2 = radPetro*radPetro;
+
+    int nPetroPix = 0;
+    float Sum = 0.0;
+    float Var = 0.0;
+
+    // set vPix to the source pixels (it may have been set to the
+    // smoothed image above)
+    psF32 **vPix = source->pixels->data.F32;
+    psF32 **vWgt = source->variance->data.F32;
+    psImageMaskType **vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+
+	psF32 yDiff = row - yCM;
+	if (fabs(yDiff) > radPetro) continue;
+
+	for (psS32 col = 0; col < source->pixels->numCols ; col++) {
+	    // check mask and value for this pixel
+	    if (vMsk && (vMsk[row][col] & maskVal)) continue;
+	    if (isnan(vPix[row][col])) continue;
+
+	    psF32 xDiff = col - xCM;
+	    if (fabs(xDiff) > radPetro) continue;
+
+	    // radPetro is just a function of (xDiff, yDiff)
+	    psF32 r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
+	    if (r2 > radPetro2) continue;
+
+	    float pDiff = vPix[row][col];
+	    psF32 wDiff = vWgt[row][col];
+
+	    Sum += pDiff;
+	    Var += wDiff;
+	    nPetroPix ++;
+	}
+    }
+
+    // return the flux and error to parameters?
+    source->extpars->petrosianFlux    = Sum;
+    source->extpars->petrosianFluxErr = sqrt(Var);
+    source->extpars->petrosianFill = nPetroPix / (M_PI * radPetro2);
+
+    // fprintf (stderr, "petro flux: %f +/- %f\n", Sum, sqrt(Var));
+
+    return true;
+}
Index: trunk/psphot/src/psphotPetrosianRadialBins.c
===================================================================
--- trunk/psphot/src/psphotPetrosianRadialBins.c	(revision 36374)
+++ trunk/psphot/src/psphotPetrosianRadialBins.c	(revision 36375)
@@ -186,4 +186,5 @@
 	psFree(values);
 	psFree(stats);
+	source->mode2 |= PM_SOURCE_MODE2_RADBIN_NAN_CENTER;
 	return true;
     }
Index: trunk/psphot/src/psphotPetrosianStats.c
===================================================================
--- trunk/psphot/src/psphotPetrosianStats.c	(revision 36374)
+++ trunk/psphot/src/psphotPetrosianStats.c	(revision 36375)
@@ -6,4 +6,5 @@
 // generate the Petrosian radius and flux from the mean surface brightness (r_i)
 
+float InterpolateValuesQuadratic (float *Xin, float *Yin, float X);
 float InterpolateValues     (float X0, float Y0, float X1, float Y1, float X);
 float InterpolateValuesErrX (float X0, float Y0, float X1, float Y1, float X, float dX0, float dX1);
@@ -20,4 +21,5 @@
     if (!profile->binSB) {
 	psLogMsg ("psphot", PS_LOG_DETAIL, "no petrosian profile, skipping source %f, %f", source->peak->xf, source->peak->yf);
+	source->mode2 |= PM_SOURCE_MODE2_PETRO_NO_PROFILE;
 	return true;
     }
@@ -53,5 +55,5 @@
     float dFsum2 = 0.0;
 
-    float nSigma = 3.0;
+    float nSigma = 2.0;
     int lowestSignificantRadius = 0;
     float lowestSignificantRatio = 1.0;
@@ -117,7 +119,19 @@
 		petRadius    = InterpolateValues     (1.0, 0.0, petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO);
 		petRadiusErr = InterpolateValuesErrX (1.0, 0.0, petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO, 0.0, petRatioErr->data.F32[nOut]);
-	    } else {
-		petRadius    = InterpolateValues     (petRatio->data.F32[nOut-1], refRadius->data.F32[nOut-1], petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO);
-		petRadiusErr = InterpolateValuesErrX (petRatio->data.F32[nOut-1], refRadius->data.F32[nOut-1], petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO, petRatioErr->data.F32[nOut-1], petRatioErr->data.F32[nOut]);
+		source->mode2 |= PM_SOURCE_MODE2_PETRO_RATIO_ZEROBIN;
+	    } else {
+	      // petRadius    = InterpolateValues     (petRatio->data.F32[nOut-1], refRadius->data.F32[nOut-1], petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO);
+	      if (nOut > 1) {
+		petRadius    = InterpolateValuesQuadratic (&petRatio->data.F32[nOut-2], &refRadius->data.F32[nOut-2],   PETROSIAN_RATIO);
+	      } else {
+		petRadius    = InterpolateValuesQuadratic (&petRatio->data.F32[nOut-3], &refRadius->data.F32[nOut-3], PETROSIAN_RATIO);
+	      }
+# if (PS_TRACE_ON)
+	      float petRadiusLinear = InterpolateValues     (petRatio->data.F32[nOut-1], refRadius->data.F32[nOut-1], petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO);
+	      if (fabs(petRadius - petRadiusLinear) > fabs(refRadius->data.F32[nOut] - refRadius->data.F32[nOut-1])) { 
+		fprintf (stderr, "big difference : %f vs %f\n", petRadius, petRadiusLinear); 
+	      }
+#endif
+	      petRadiusErr = InterpolateValuesErrX (petRatio->data.F32[nOut-1], refRadius->data.F32[nOut-1], petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO, petRatioErr->data.F32[nOut-1], petRatioErr->data.F32[nOut]);
 	    }
 	    above = false;
@@ -148,4 +162,5 @@
 	    fprintf (stderr, "nan pet radius\n");
 	}
+	source->mode2 |= PM_SOURCE_MODE2_PETRO_INSIG_RATIO;
     }
 
@@ -180,4 +195,5 @@
     bool found50 = false;
     bool found90 = false;
+
     // XXX use bisection to do this faster:
     for (int i = 0; !(found50 && found90) && i < refRadius->n; i++) {
@@ -235,4 +251,23 @@
 }
 
+// Lagrange's form of the interpolating polynomial...
+float InterpolateValuesQuadratic (float *Xin, float *Yin, float X) {
+
+  float dx01 = Xin[0] - Xin[1];
+  float dx02 = Xin[0] - Xin[2];
+  float dx12 = Xin[1] - Xin[2];
+
+  float dx0  = X - Xin[0];
+  float dx1  = X - Xin[1];
+  float dx2  = X - Xin[2];
+
+  float y0 = Yin[0]*dx1*dx2/(dx01*dx02);
+  float y1 = Yin[1]*dx0*dx2/(dx01*dx12); // need - sign
+  float y2 = Yin[2]*dx0*dx1/(dx02*dx12); 
+
+  float Y = y0 - y1 + y2;
+  return Y;
+}
+
 float InterpolateValues (float X0, float Y0, float X1, float Y1, float X) {
     float dydx = (Y1 - Y0) / (X1 - X0);
Index: trunk/psphot/src/psphotRadialBins.c
===================================================================
--- trunk/psphot/src/psphotRadialBins.c	(revision 36374)
+++ trunk/psphot/src/psphotRadialBins.c	(revision 36375)
@@ -44,12 +44,8 @@
     psVector *radMin = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
     psVector *radMax = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
-    if (!radMin || !radMin->n) {
-	psError (PSPHOT_ERR_CONFIG, true, "error in definition of annular bins (radMin missing or empty)");
-	return false;
-    }
-    if (!radMax || !radMax->n) {
-	psError (PSPHOT_ERR_CONFIG, true, "error in definition of annular bins (radMax missing or empty)");
-	return false;
-    }
+    psAssert (radMin, "RADIAL.ANNULAR.BINS.LOWER is missing from recipe");
+    psAssert (radMin->n, "RADIAL.ANNULAR.BINS.LOWER is empty in recipe");
+    psAssert (radMax, "RADIAL.ANNULAR.BINS.UPPER is missing from recipe");
+    psAssert (radMax->n, "RADIAL.ANNULAR.BINS.UPPER is empty in recipe");
 
     psVector *binSB      = psVectorAllocEmpty(radMin->n, PS_TYPE_F32); // surface brightness of radial bin
@@ -161,4 +157,5 @@
 	psFree(values);
 	psFree(stats);
+	source->mode2 |= PM_SOURCE_MODE2_RADBIN_NAN_CENTER;
 	return true;
     }
Index: trunk/psphot/src/psphotRadialProfile.c
===================================================================
--- trunk/psphot/src/psphotRadialProfile.c	(revision 36374)
+++ trunk/psphot/src/psphotRadialProfile.c	(revision 36375)
@@ -1,3 +1,11 @@
 # include "psphotInternal.h"
+
+static int Nskip1 = 0;
+static int Nskip2 = 0;
+static int Nskip3 = 0;
+static int Nskip4 = 0;
+static int Nskip5 = 0;
+
+# define SKIP(VALUE) { VALUE++; return false; }
 
 bool psphotRadialProfile (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal) {
@@ -23,5 +31,5 @@
     if (!psphotRadialProfilesByAngles (source, Nsec, Rmax)) {
 	psError (PS_ERR_UNKNOWN, false, "failed to measure radial profile for petrosian");
-	return false;
+	SKIP (Nskip1);
     }
     // allocate: extpars->radFlux->radii,fluxes,theta
@@ -32,5 +40,5 @@
     if (!psphotRadiiFromProfiles (source, fluxMin, fluxMax)) {
 	psError (PS_ERR_UNKNOWN, false, "failed to measure isophotal radii from profiles");
-	return false;
+	SKIP (Nskip2);
     }
     // allocate : extpars->radFlux->isophotalRadii (use profile->radii,fluxes)
@@ -40,5 +48,5 @@
     if (!psphotEllipticalContour (source)) {
 	// psLogMsg ("psphot", 3, "failed to measure elliptical contour");
-	return false;
+	SKIP (Nskip3);
     }
     // use extpars->radFlux->isophotalRadii,theta (result in extpars->axes)
@@ -48,5 +56,5 @@
     if (!psphotEllipticalProfile (source, RAW_RADIUS)) {
 	psError (PS_ERR_UNKNOWN, false, "failed to generate elliptical profile");
-	return false;
+	SKIP (Nskip4);
     }
     // allocate extpars->ellipticalFlux->radiusElliptical,fluxElliptical (use axes to scale raw pixels)
@@ -55,5 +63,5 @@
     if (!psphotRadialBins (recipe, source, Rmax, skynoise)) {
 	psError (PS_ERR_UNKNOWN, false, "failed to generate radial bins");
-	return false;
+	SKIP (Nskip5);
     }
     // allocate extpars->radProfile->binSB, binSBstdv, binSum, binFill, radialBins, area (small lengths)
@@ -62,2 +70,12 @@
     return true;
 }
+
+void psphotRadialProfileShowSkips () {
+# if (PS_TRACE_ON)
+  fprintf (stderr, "radial profile skipped @ 1  : %d\n", Nskip1);
+  fprintf (stderr, "radial profile skipped @ 2  : %d\n", Nskip2);
+  fprintf (stderr, "radial profile skipped @ 3  : %d\n", Nskip3);
+  fprintf (stderr, "radial profile skipped @ 4  : %d\n", Nskip4);
+  fprintf (stderr, "radial profile skipped @ 5  : %d\n", Nskip5);
+#endif
+}
Index: trunk/psphot/src/psphotReadout.c
===================================================================
--- trunk/psphot/src/psphotReadout.c	(revision 36374)
+++ trunk/psphot/src/psphotReadout.c	(revision 36375)
@@ -316,4 +316,11 @@
     psphotSourceSize (config, view, filerule, false); // pass 2 (detections->allSources)
 
+    // XXX currently we are doing both the analysis of the size and the assessment of "fit ext"
+    // in source size.  this overloads the bit MODE_EXT_LIMIT to mean "fit ext" not just
+    // "bigger than a PSF"
+
+    // decide which source(s) are to be fitted with the extended source analysis code.
+    psphotChooseAnalysisOptions (config, view, filerule);
+
     psphotExtendedSourceAnalysis (config, view, filerule); // pass 1 (detections->allSources)
     psphotExtendedSourceFits (config, view, filerule); // pass 1 (detections->allSources)
Index: trunk/psphot/src/psphotRoughClass.c
===================================================================
--- trunk/psphot/src/psphotRoughClass.c	(revision 36374)
+++ trunk/psphot/src/psphotRoughClass.c	(revision 36375)
@@ -87,5 +87,5 @@
     if (NX > NY) {
 	NXuse = ScaleForClump;
-	NYuse = (int) (ScaleForClump * (NY / NX) + 0.5);
+	NYuse = (int) (ScaleForClump * (NX / NY) + 0.5);
     } else {
 	NYuse = ScaleForClump;
Index: trunk/psphot/src/psphotSetThreads.c
===================================================================
--- trunk/psphot/src/psphotSetThreads.c	(revision 36374)
+++ trunk/psphot/src/psphotSetThreads.c	(revision 36375)
@@ -47,4 +47,19 @@
     psFree(task);
 
+    task = psThreadTaskAlloc("PSPHOT_KRON_FLUX", 4);
+    task->function = &psphotKronFlux_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_PETRO_FLUX", 4);
+    task->function = &psphotPetroFlux_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_GALAXY_SHAPES", 7);
+    task->function = &psphotGalaxyShape_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
     task = psThreadTaskAlloc("PSPHOT_BLEND_FIT", 10);
     task->function = &psphotBlendFit_Threaded;
Index: trunk/psphot/src/psphotSourceFits.c
===================================================================
--- trunk/psphot/src/psphotSourceFits.c	(revision 36374)
+++ trunk/psphot/src/psphotSourceFits.c	(revision 36375)
@@ -19,4 +19,7 @@
 static int NfitIterPCM = 0;
 static int NfitPixPCM = 0;
+
+bool psphotPCMfitCheckSize (pmPCMdata *pcm, pmSource *source, psImageMaskType maskVal, float psfSize);
+bool psphotPCMfitRetry (pmPCMdata *pcm, pmSource *source, pmSourceFitOptions *fitOptions, psImageMaskType maskVal, psImageMaskType markVal, float psfSize);
 
 bool psphotFitInit (int nThreads) {
@@ -579,12 +582,10 @@
     maskVal |= markVal;
 
-    // allocate the model
+    // allocate the model (this can only fail on a config error)
     pmModel *model = pmModelAlloc(modelType);
-    if (!model) {
-	return NULL;
-    }
-
-    float t1, t2, t4, t5;
-    t1 = t2 = t4 = t5 = 0.0;
+    psAssert (model, "invalid extended model name");
+
+    float t1, t2, t3, t4, t5;
+    t1 = t2 = t3 = t4 = t5 = 0.0;
     if (TIMING) { psTimerStart ("psphotFitPCM"); }
 
@@ -619,5 +620,5 @@
 	if (!psphotSersicModelGuessPCM (pcm, source, maskVal, psfSize)) {
 	    psFree (pcm);
-	    model->flags |= PM_MODEL_STATUS_BADARGS;
+	    model->flags |= PM_MODEL_SERSIC_PCM_FAIL_GUESS;
 	    return model;
 	}
@@ -628,5 +629,5 @@
 	if (!psphotFitSersicShapeAndIndexGridAuto (pcm, readout, source, &options, maskVal, markVal, psfSize)) {
 	    psFree (pcm);
-	    model->flags |= PM_MODEL_STATUS_BADARGS;
+	    model->flags |= PM_MODEL_SERSIC_PCM_FAIL_GRID;
 	    psError(PS_ERR_UNKNOWN, true, "Failed to find a index & shape");
 	    psErrorClear (); // clear the polynomial error
@@ -637,10 +638,10 @@
 	if (!pmSourceModelGuessPCM (pcm, source, maskVal, markVal)) {
 	    psFree (pcm);
-	    model->flags |= PM_MODEL_STATUS_BADARGS;
+	    model->flags |= PM_MODEL_PCM_FAIL_GUESS;
 	    return model;
 	}
     }
 
-    if (TIMING) { t4 = psTimerMark ("psphotFitPCM"); }
+    if (TIMING) { t3 = psTimerMark ("psphotFitPCM"); }
 
     // psTraceSetLevel("psLib.math.psMinimizeLMChi2", 5);
@@ -648,9 +649,18 @@
     NfitIterPCM += pcm->modelConv->nIter;
     NfitPixPCM += pcm->modelConv->nDOF;
+    if (TIMING) { t4 = psTimerMark ("psphotFitPCM"); }
+
+    // XXX we might make this more efficient by setting NITER to be fairly small.  if we hit the iteration
+    // limit, then we could do a small grid search on the size and try again from the best fit 
+
+    if (options.isInteractive) psphotPCMfitCheckSize (pcm, source, maskVal, psfSize);
+    // if (pcm->modelConv->nIter == fitOptions->nIter) {
+    // 	psphotPCMfitRetry (pcm, source, &options, maskVal, markVal, psfSize);
+    // }
     if (TIMING) { t5 = psTimerMark ("psphotFitPCM"); }
 
     if (TIMING) {
 	int nPixBig = source->pixels->numCols * source->pixels->numRows;
-    	fprintf (stderr, "psphotFitPCM : nIter: %2d, radius: %6.1f, npix: %5d of %5d, t1: %6.4f, t2: %6.4f, t4: %6.4f, t5: %6.4f\n", model->nIter, model->fitRadius, model->nPix, nPixBig, t1, t2, t4, t5);
+    	fprintf (stderr, "psphotFitPCM : nIter: %2d, radius: %6.1f, npix: %5d of %5d, t1: %6.4f, t2: %6.4f, t3: %6.4f, t4: %6.4f, t5: %6.4f\n", model->nIter, model->fitRadius, model->nPix, nPixBig, t1, t2, t3, t4, t5);
     }
     if (EXTRA_VERBOSE && !TIMING) {
@@ -778,10 +788,14 @@
 }
 
-// float indexGuessInv[] = {0.5, 0.33, 0.25, 0.167, 0.125, 0.083};
-float indexGuessInv[] = {0.5, 0.4, 0.3, 0.25, 0.20, 0.15, 0.125};
-# define N_INDEX_GUESS_INV 7
-
-// float reffGuess[] = {3.0, 10.0, 20.0, 30.0, 40.0};
-float reffGuess[] = {0.5, 0.75, 1.0, 1.4, 2.0};
+// 0.5 / n for (1.0, 1.25, 1.66, 2.0, 3.33, 4.0)
+// float indexGuessInv[] = {0.5, 0.4, 0.3, 0.25, 0.20, 0.15, 0.125};
+
+// 0.5 / n for (0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0)
+float indexGuessInv[] = {1.00, 0.50, 0.333, 0.25, 0.166, 0.125, 0.10, 0.0833};
+float indexGuessR1q[] = {1.06, 1.19, 1.335, 1.48, 1.840, 2.290, 2.84, 3.5300};
+# define N_INDEX_GUESS_INV 8
+
+// we are going to guess in fractions about the R1-based guess
+float reffGuess[] = {0.8, 0.9, 1.0, 1.12, 1.25};
 # define N_REFF_GUESS 5
 
@@ -791,5 +805,5 @@
 
     // we get a reasonable guess from:
-    // * Reff = Kron R1
+    // * Reff = Kron R1 / Q(index) -- Q comes from Graham & Driver 
     // * Rmajor / Rminor & Theta from moments
     // * Io from total Kron flux
@@ -813,18 +827,4 @@
     psEllipseAxes momentAxes = psEllipseMomentsToAxes (moments, 20.0);
 
-    if (0) {
-	psEllipseAxes guessAxes;
-	guessAxes.major = source->moments->Mrf;
-	guessAxes.minor = (momentAxes.minor / momentAxes.minor) * guessAxes.major;
-	guessAxes.theta = momentAxes.theta;
-
-	if (!isfinite(guessAxes.major)) return false;
-	if (!isfinite(guessAxes.minor)) return false;
-	if (!isfinite(guessAxes.theta)) return false;
-
-	// convert the major,minor,theta to shape parameters for an Reff-like model
-	pmModelAxesToParams (&PAR[PM_PAR_SXX], &PAR[PM_PAR_SXY], &PAR[PM_PAR_SYY], guessAxes, true);
-    }
-
     // set the model position
     if (!pmModelSetPosition(&PAR[PM_PAR_XPOS], &PAR[PM_PAR_YPOS], source)) {
@@ -846,22 +846,22 @@
     // loop over index guesses and find the best fit
     for (int j = 0; j < N_REFF_GUESS; j++) {
-
-	psEllipseAxes guessAxes;
-	guessAxes.major = reffGuess[j] * source->moments->Mrf;
-	guessAxes.minor = guessAxes.major * (momentAxes.minor / momentAxes.major);
-	guessAxes.theta = momentAxes.theta;
-
-	if (!isfinite(guessAxes.major)) return false;
-	if (!isfinite(guessAxes.minor)) return false;
-	if (!isfinite(guessAxes.theta)) return false;
-
-	// convert the major,minor,theta to shape parameters for an Reff-like model
-	pmModelAxesToParams (&PAR[PM_PAR_SXX], &PAR[PM_PAR_SXY], &PAR[PM_PAR_SYY], guessAxes, true);
-
 	for (int i = 0; i < N_INDEX_GUESS_INV; i++) {
 	    PAR[PM_PAR_7] = indexGuessInv[i];
 
+	    psEllipseAxes guessAxes;
+	    guessAxes.major = reffGuess[j] * source->moments->Mrf / indexGuessR1q[i];
+	    guessAxes.minor = guessAxes.major * (momentAxes.minor / momentAxes.major);
+	    guessAxes.theta = momentAxes.theta;
+
+	    if (!isfinite(guessAxes.major)) return false;
+	    if (!isfinite(guessAxes.minor)) return false;
+	    if (!isfinite(guessAxes.theta)) return false;
+
+	    // convert the major,minor,theta to shape parameters for an Reff-like model
+	    pmModelAxesToParams (&PAR[PM_PAR_SXX], &PAR[PM_PAR_SXY], &PAR[PM_PAR_SYY], guessAxes, true);
+
 	    // generated the modelFlux
-	    pmPCMMakeModel (source, pcm->modelConv, maskVal, psfSize);
+	    // XXX note that this does not add sky to model
+	    pmPCMMakeModel (source, pcm->modelConv, pcm->nsigma, maskVal, psfSize);
 	
 	    float YY = 0.0;
@@ -901,8 +901,10 @@
 		iMin = Io;
 		sMin = indexGuessInv[i];
-		rMin = reffGuess[j];
+		rMin = reffGuess[j] / indexGuessR1q[i];
 	    }
-	    // fprintf (stderr, "%d | %f %f %f %f | %f %f %f %f", i, indexGuessInv[i], reffGuess[j], Io, Chisq, sMin, rMin, iMin, xMin);
-	    // fprintf (stderr, "\n");
+            if (EXTRA_VERBOSE) {
+                fprintf (stderr, "%d | %f %f %f %f | %f %f %f %f", i, indexGuessInv[i], reffGuess[j], Io, Chisq, sMin, rMin, iMin, xMin);
+                fprintf (stderr, "\n");
+            }
 	}
     }
@@ -1007,5 +1009,20 @@
     psVector *Sidx = psVectorAllocEmpty (16, PS_TYPE_F32);
 
-    PAR[PM_PAR_7] = indexGuessInv[nStart];
+    float Sm = NAN, Sp = NAN, So = NAN;
+    if (nStart == 0) {
+	Sm = indexGuessInv[nStart];
+	So = 0.5*(indexGuessInv[nStart + 1] + indexGuessInv[nStart]);
+	Sp = indexGuessInv[nStart + 1];
+    } else if (nStart == N_INDEX_GUESS_INV - 1) {
+	Sp = indexGuessInv[nStart];
+	So = 0.5*(indexGuessInv[nStart - 1] + indexGuessInv[nStart]);
+	Sm = indexGuessInv[nStart - 1];
+    } else {
+	Sm = 0.5*(indexGuessInv[nStart - 1] + indexGuessInv[nStart]);
+	So = indexGuessInv[nStart];
+	Sp = 0.5*(indexGuessInv[nStart + 1] + indexGuessInv[nStart]);
+    }
+    
+    PAR[PM_PAR_7] = Sm;
     pmSourceFitPCM (pcm, source, &options, maskVal, markVal, psfSize);
     if (EXTRA_VERBOSE) fprintf (stderr, "%d >>> %d %f : %f - %f %f - %f %f %f - %f\n", source->id, model->nIter, model->chisqNorm, PAR[7], PAR[2], PAR[3], PAR[4], PAR[5], PAR[6], PAR[1]);
@@ -1013,5 +1030,5 @@
     psVectorAppend (chi2, model->chisqNorm);
 
-    PAR[PM_PAR_7] = (nStart < N_INDEX_GUESS_INV - 1) ? 0.5*(indexGuessInv[nStart + 1] + indexGuessInv[nStart]) : 0.1;
+    PAR[PM_PAR_7] = So;
     pmSourceFitPCM (pcm, source, &options, maskVal, markVal, psfSize);
     if (EXTRA_VERBOSE) fprintf (stderr, "%d >>> %d %f : %f - %f %f - %f %f %f - %f\n", source->id, model->nIter, model->chisqNorm, PAR[7], PAR[2], PAR[3], PAR[4], PAR[5], PAR[6], PAR[1]);
@@ -1019,23 +1036,9 @@
     psVectorAppend (chi2, model->chisqNorm);
 
-    PAR[PM_PAR_7] = (nStart > 0) ? 0.5*(indexGuessInv[nStart - 1] + indexGuessInv[nStart]) : 0.55;
+    PAR[PM_PAR_7] = Sp;
     pmSourceFitPCM (pcm, source, &options, maskVal, markVal, psfSize);
     if (EXTRA_VERBOSE) fprintf (stderr, "%d >>> %d %f : %f - %f %f - %f %f %f - %f\n", source->id, model->nIter, model->chisqNorm, PAR[7], PAR[2], PAR[3], PAR[4], PAR[5], PAR[6], PAR[1]);
     psVectorAppend (Sidx, 100*PAR[PM_PAR_7]);
     psVectorAppend (chi2, model->chisqNorm);
-
-    if (chi2->data.F32[1] < chi2->data.F32[2]) {
-      if (nStart == N_INDEX_GUESS_INV - 1) {
-	PAR[PM_PAR_7] = 0.11;
-      } else {
-	PAR[PM_PAR_7] = indexGuessInv[nStart + 1];
-      }
-    } else {
-      if (nStart == 0) {
-	PAR[PM_PAR_7] = 0.52;
-      } else {
-	PAR[PM_PAR_7] = indexGuessInv[nStart - 1];
-      }
-    }
 
     psPolynomial1D *poly = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 2);
@@ -1051,7 +1054,11 @@
     float Smin = -0.5 * poly->coeff[1] / poly->coeff[2] / 100.0;
 
-    // constrain Smin to be in a valid range (1.0 - 0.1, corresponding to 0.5 (Gauss) to 5.0 (slightly peakier than Dev)
-    Smin = PS_MAX(PS_MIN(Smin, 1.0), 0.1);
+    // constrain Smin to be in a valid range: allow the fitted range to go a bit beyond the 3 trial points, but no further
+    float Smx = Sm - 0.25*(So - Sm);
+    float Spx = Sp + 0.25*(Sp - So);
+    Smin = PS_MAX(PS_MIN(Smin, Smx), Spx);
     PAR[PM_PAR_7] = Smin;
+
+    // XXX I could set the error on PAR_7 here if I knew how to roughly convert these chisq values to true chisq values
 
     // return to the original fitting mode (fitOptions)
@@ -1246,3 +1253,162 @@
 }
 
- 
+// # define N_REFF_CHECK 11
+// float drefCheck[] = {-0.02, -0.04, -0.06, 0.0, 0.85, 0.90, 0.95, 1.00, 1.05, 1.10, 1.15, 1.20, 1.25};
+
+// we have an initial fit, check to see if the current size is besst
+bool psphotPCMfitCheckSize (pmPCMdata *pcm, pmSource *source, psImageMaskType maskVal, float psfSize) {
+
+    // PAR is already at my current best guess
+    psF32 *PAR = pcm->modelConv->params->data.F32;
+
+    // store best guess as a shape
+    psEllipseAxes centerAxes;
+    pmModelParamsToAxes (&centerAxes, PAR[PM_PAR_SXX], PAR[PM_PAR_SXY], PAR[PM_PAR_SYY], true);
+
+    float xMin = NAN;
+    float iMin = NAN;
+    float rMin = NAN;
+
+    // loop over Reff, keeping the ARatio and Theta constant
+    for (int j = -4; j <= 4; j++) {
+
+	float dref = j * 0.01;
+
+	psEllipseAxes guessAxes;
+	guessAxes.major = centerAxes.major + dref;
+	guessAxes.minor = guessAxes.major * centerAxes.minor / centerAxes.major;
+	guessAxes.theta = centerAxes.theta;
+
+	if (!isfinite(guessAxes.major)) return false;
+	if (!isfinite(guessAxes.minor)) return false;
+	if (!isfinite(guessAxes.theta)) return false;
+
+	// convert the major,minor,theta to shape parameters for an Reff-like model
+	pmModelAxesToParams (&PAR[PM_PAR_SXX], &PAR[PM_PAR_SXY], &PAR[PM_PAR_SYY], guessAxes, true);
+
+	// generated the modelFlux
+	    // XXX note that this does not add sky to model
+	pmPCMMakeModel (source, pcm->modelConv, pcm->nsigma, maskVal, psfSize);
+	
+	float YY = 0.0;
+	float YM = 0.0;
+	float MM = 0.0;
+	bool usePoisson = false;
+
+	for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	    for (int ix = 0; ix < source->pixels->numCols; ix++) {
+		// skip masked points
+		if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix]) {
+		    continue;
+		}
+		// skip zero-variance points
+		if (source->variance->data.F32[iy][ix] == 0) {
+		    continue;
+		}
+		// skip nan value points
+		if (!isfinite(source->pixels->data.F32[iy][ix])) {
+		    continue;
+		}
+
+		float fy = source->pixels->data.F32[iy][ix];
+		float fm = source->modelFlux->data.F32[iy][ix];
+		float wt = (usePoisson) ? 1.0 / source->variance->data.F32[iy][ix] : 1.0;
+
+		YY += PS_SQR(fy) * wt;
+		YM += fm * fy * wt;
+		MM += PS_SQR(fm) * wt;
+	    }
+	}
+
+	float Io = YM / MM;
+	float Chisq = YY - 2 * Io * YM + Io * Io * MM;
+	if (isnan(xMin) || (Chisq < xMin)) {
+	    xMin = Chisq;
+	    iMin = Io;
+	    rMin = dref;
+	}
+	// fprintf (stderr, "%d | %f %f %f | %f %f %f\n", j, dref, Io, Chisq, rMin, iMin, xMin);
+    }
+
+    psEllipseAxes guessAxes;
+    guessAxes.major = centerAxes.major + rMin;
+    guessAxes.minor = guessAxes.major * centerAxes.minor / centerAxes.major;
+    guessAxes.theta = centerAxes.theta;
+
+    if (!isfinite(guessAxes.major)) return false;
+    if (!isfinite(guessAxes.minor)) return false;
+    if (!isfinite(guessAxes.theta)) return false;
+
+    // convert the major,minor,theta to shape parameters for an Reff-like model
+    pmModelAxesToParams (&PAR[PM_PAR_SXX], &PAR[PM_PAR_SXY], &PAR[PM_PAR_SYY], guessAxes, true);
+    PAR[PM_PAR_I0] = iMin;
+
+    return true;
+}
+
+// we have an initial fit, check to see if the current size is besst
+bool psphotPCMfitRetry (pmPCMdata *pcm, pmSource *source, pmSourceFitOptions *fitOptions, psImageMaskType maskVal, psImageMaskType markVal, float psfSize) {
+
+    // PAR is already at my current best guess
+    psF32 *PAR = pcm->modelConv->params->data.F32;
+
+    // store best guess as a shape
+    psEllipseAxes centerAxes;
+    pmModelParamsToAxes (&centerAxes, PAR[PM_PAR_SXX], PAR[PM_PAR_SXY], PAR[PM_PAR_SYY], true);
+
+    // retry with axes smaller by 1 pixel
+    psEllipseAxes guessAxes;
+    guessAxes.major = centerAxes.major - 0.08;
+    guessAxes.minor = guessAxes.major * centerAxes.minor / centerAxes.major;
+    guessAxes.theta = centerAxes.theta;
+
+    if (!isfinite(guessAxes.major)) return false;
+    if (!isfinite(guessAxes.minor)) return false;
+    if (!isfinite(guessAxes.theta)) return false;
+
+    // convert the major,minor,theta to shape parameters for an Reff-like model
+    pmModelAxesToParams (&PAR[PM_PAR_SXX], &PAR[PM_PAR_SXY], &PAR[PM_PAR_SYY], guessAxes, true);
+
+    // generated the modelFlux
+	    // XXX note that this does not add sky to model
+    pmPCMMakeModel (source, pcm->modelConv, pcm->nsigma, maskVal, psfSize);
+	
+    float YY = 0.0;
+    float YM = 0.0;
+    float MM = 0.0;
+    bool usePoisson = false;
+
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	for (int ix = 0; ix < source->pixels->numCols; ix++) {
+	    // skip masked points
+	    if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix]) {
+		continue;
+	    }
+	    // skip zero-variance points
+	    if (source->variance->data.F32[iy][ix] == 0) {
+		continue;
+	    }
+	    // skip nan value points
+	    if (!isfinite(source->pixels->data.F32[iy][ix])) {
+		continue;
+	    }
+
+	    float fy = source->pixels->data.F32[iy][ix];
+	    float fm = source->modelFlux->data.F32[iy][ix];
+	    float wt = (usePoisson) ? 1.0 / source->variance->data.F32[iy][ix] : 1.0;
+
+	    YY += PS_SQR(fy) * wt;
+	    YM += fm * fy * wt;
+	    MM += PS_SQR(fm) * wt;
+	}
+    }
+
+    float Io = YM / MM;
+    PAR[PM_PAR_I0] = Io;
+
+    pmSourceFitPCM (pcm, source, fitOptions, maskVal, markVal, psfSize);  // NOTE : 1687 allocs in here
+
+    return true;
+}
+
+
Index: trunk/psphot/src/psphotSourceSize.c
===================================================================
--- trunk/psphot/src/psphotSourceSize.c	(revision 36374)
+++ trunk/psphot/src/psphotSourceSize.c	(revision 36375)
@@ -1,5 +1,5 @@
 # include "psphotInternal.h"
-# include <gsl/gsl_sf_gamma.h>
-
+
+// this structure is only used internally to simplify the function parameters
 typedef struct {
     psImageMaskType maskVal;
@@ -13,7 +13,4 @@
     bool altDiffExt;
     float altDiffExtThresh;
-    bool extFitAll;
-    bool extFitAllReadout;
-    float extFitAllThresh;
     float soft;
     int grow;
@@ -33,5 +30,4 @@
 bool psphotSourceSelectCR (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options);
 bool psphotMaskCosmicRay (pmReadout *readout, pmSource *source, psImageMaskType maskVal, int maxWindowCR);
-bool psphotMaskCosmicRayFootprintCheck (psArray *sources);
 int  psphotMaskCosmicRayConnected (int xPeak, int yPeak, psImage *mymask, psImage *myvar, psImage *edges, int binning, float sigma_thresh);
 float psphotSourceSizeFindThreshold (psVector *value, psVector *mask, int maskValue, float minValue, float maxValue, float delta, float guess, float fraction);
@@ -41,8 +37,5 @@
 
 // we need to call this function after sources have been fitted to the PSF model and
-// subtracted.  To determine the CR-nature, this function examines the 9 pixels in the 3x3
-// square containing the peak and compares the observed flux to the model.  To determine
-// the EXT-nature, this function measures the amount of positive or negative total
-// deviation from the psf model at the r = FWHM/2 position
+// subtracted.  
 
 // for now, let's store the detections on the readout->analysis for each readout
@@ -121,21 +114,16 @@
     assert (status);
 
-    // XXX recipe name is not great
+    // XXX recipe name is not great (NOTE : not used!)
     options.nSigmaMoments = psMetadataLookupF32 (&status, recipe, "PSPHOT.EXT.NSIGMA.MOMENTS");
     assert (status);
 
-    // Optional extended source measurement algorithm to improve diff image trails
+    // Optional algorithm to define if a source is extended (used by DIFF analysis)
     options.altDiffExt = psMetadataLookupBool(&status, recipe, "PSPHOT.EXT.DIFF.ALTERNATE");
     assert (status);
+
     // Threshold for this alternate method
     options.altDiffExtThresh = psMetadataLookupF32(&status, recipe, "PSPHOT.EXT.DIFF.ALTERNATE.THRESH");
     assert (status);
-    // Option to enable fitting of all objects with extended model.
-    options.extFitAll = psMetadataLookupBool(&status, recipe, "PSPHOT.EXT.FIT.ALL.SOURCES");
-    assert (status);
-    // Fitting everything is fine, but if the source density is high, we probably shouldn't.
-    options.extFitAllThresh = psMetadataLookupF32(&status, recipe, "PSPHOT.EXT.FIT.ALL.THRESH");
-    assert (status);
-    
+
     // location of a single test source
     options.xtest = psMetadataLookupS32 (&status, recipe, "PSPHOT.CRMASK.XTEST");
@@ -422,19 +410,5 @@
 
     psLogMsg("psModules.objects", PS_LOG_INFO, "Source Size classifications: %4s %4s %4s %4s %4s", "Npsf", "Next", "Nsat", "Ncr", "Nskip");
-    // Determine if this readout is above the threshold to ext fit all sources
-    options->extFitAllReadout = false;
-    if (options->extFitAll) {
-      float maskFrac = psMetadataLookupF32(&status,readout->analysis,"READOUT.MASK.FRAC");
-      if (status) {
-	maskFrac = 0.0;
-      }
-      if (sources->n * (1.0 - maskFrac) > options->extFitAllThresh) {
-	options->extFitAllReadout = false;
-      }
-      else {
-	options->extFitAllReadout = true;
-      }
-    }
-    
+
     if (!psphotSourceClassRegion (NULL, &psfClump, sources, recipe, psf, options)) {
 	psLogMsg ("psphot", 4, "Failed to determine source classification for full image\n");
@@ -444,4 +418,5 @@
     return true;
     
+    // NOTE : this section is deactivated (EAM : I think we were getting poor boundary effects?)
     int nRegions = psMetadataLookupS32 (&status, readout->analysis, "PSF.CLUMP.NREGIONS");
     for (int i = 0; i < nRegions; i ++) {
@@ -582,10 +557,4 @@
         // * SAT stars should not be faint, but defects may?
 
-	// If the recipe requests we do extended source fits to everything, set
-	// the EXT_LIMIT flag
-	if (options->extFitAllReadout) {
-	  psTrace("psphotSourceClassRegion.EXTALT",10,"In extFitAll: %d %d\n",options->extFitAll,options->extFitAllReadout);
-	  source->mode |= PM_SOURCE_MODE_EXT_LIMIT;
-	}
         // Defects may not always match CRs from peak curvature analysis
         // Defects may also be marked as SATSTAR -- XXX deactivate this flag?
@@ -595,5 +564,6 @@
 
         // saturated star (too many saturated pixels or peak above saturation limit).  These
-        // may also be saturated galaxies, or just large saturated regions.
+        // may also be saturated galaxies, or just large saturated regions.  They are never
+        // marked as 'extended'
         if (source->mode & PM_SOURCE_MODE_SATSTAR) {
             psTrace("psphotSourceClassRegion.SAT",4,"CLASS: %g %g\t%g %g\t%g %g\t%g %g\t%g SAT\n",
@@ -604,5 +574,6 @@
         }
 
-        // any sources missing a large fraction should just be treated as PSFs
+        // any sources missing a large fraction should just be treated as PSFs. They are never
+        // marked as 'extended'
         if ((source->pixWeightNotBad < 0.9) || (source->pixWeightNotPoor < 0.9)) {
             psTrace("psphotSourceClassRegion.PSF",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g PSF\t%g %g\n",
@@ -634,4 +605,5 @@
             psTrace("psphotSourceClassRegion.EXT",4,"CLASS: %g %g\t%g %g\t%g %g\t%g %g\t%g EXT\n",
                     source->peak->xf, source->peak->yf, Mminor, kMag, dMag, nSigmaMAG, options->sizeLimitCR, options->magLimitCR, options->nSigmaApResid);
+	    source->type = PM_SOURCE_TYPE_EXTENDED;
             source->mode |= PM_SOURCE_MODE_EXT_LIMIT;
             source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
@@ -639,4 +611,5 @@
             continue;
         }
+
 	// Alternate extended source limit calculation
 	if (options->altDiffExt) {
@@ -652,4 +625,5 @@
                     source->peak->xf, source->peak->yf, Mminor, kMag, dMag, nSigmaMAG, options->sizeLimitCR, options->magLimitCR, options->nSigmaApResid,
 		    momentRatioVeres,options->altDiffExtThresh);
+	    source->type = PM_SOURCE_TYPE_EXTENDED;
             source->mode |= PM_SOURCE_MODE_EXT_LIMIT;
             source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
@@ -968,69 +942,4 @@
 }
 
-bool psphotMaskCosmicRayFootprintCheck (psArray *sources) {
-#ifdef CHECK_FOOTPRINTS
-    // This gets really expensive for complex images
-    for (int i = 0; i < sources->n; i++) {
-        pmSource *source = sources->data[i];
-        pmPeak *peak = source->peak;
-        pmFootprint *footprint = peak->footprint;
-        if (!footprint) continue;
-        for (int j = 0; j < footprint->spans->n; j++) {
-            pmSpan *sp = footprint->spans->data[j];
-            psAssert (sp, "missing span");
-        }
-    }
-#endif
-    return true;
-}
-
-/**** ------ old versions of cosmic ray masking ----- ****/
-
-bool psphotMaskCosmicRayIsophot (pmSource *source, psImageMaskType maskVal, psImageMaskType crMask);
-
-// This attempt to mask the cosmic rays used the isophotal boundary
-bool psphotMaskCosmicRay_V1 (psImage *mask, pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
-
-    // replace the source flux
-    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
-
-    // flag this as a CR
-    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
-    pmPeak *peak = source->peak;
-    psAssert (peak, "NULL peak");
-
-    // grab the matching footprint
-    pmFootprint *footprint = peak->footprint;
-    if (!footprint) {
-      psTrace("psphot.czw",2,"Using isophot CR mask code.");
-
-        // if we have not footprint, use the old code to mask by isophot
-        psphotMaskCosmicRayIsophot (source, maskVal, crMask);
-        return true;
-    }
-
-    if (!footprint->spans) {
-      psTrace("psphot.czw",2,"Using isophot CR mask code.");
-
-        // if we have no footprint, use the old code to mask by isophot
-        psphotMaskCosmicRayIsophot (source, maskVal, crMask);
-        return true;
-    }
-    psphotMaskCosmicRayIsophot (source, maskVal, crMask);
-    // mask all of the pixels covered by the spans of the footprint
-    for (int j = 1; j < footprint->spans->n; j++) {
-        pmSpan *span1 = footprint->spans->data[j];
-
-        int iy = span1->y;
-        int xs = span1->x0;
-        int xe = span1->x1;
-
-        for (int ix = xs; ix < xe; ix++) {
-            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
-        }
-    }
-    return true;
-}
-
 # define VERBOSE 0
 int psphotMaskCosmicRayConnected (int xPeak, int yPeak, psImage *mymask, psImage *myvar, psImage *edges, int binning, float sigma_thresh) {
@@ -1176,278 +1085,4 @@
 }
 
-bool psphotMaskCosmicRayIsophot (pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
-
-    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
-    pmPeak *peak = source->peak;
-    psAssert (peak, "NULL peak");
-
-    psImage *mask   = source->maskView;
-    psImage *pixels = source->pixels;
-    psImage *variance = source->variance;
-
-    // XXX This should be a recipe variable
-# define SN_LIMIT 5.0
-
-    int xo = peak->x - pixels->col0;
-    int yo = peak->y - pixels->row0;
-
-    // mark the pixels in this row to the left, then the right
-    for (int ix = xo; ix >= 0; ix--) {
-        float SN = pixels->data.F32[yo][ix] / sqrt(variance->data.F32[yo][ix]);
-        if (SN > SN_LIMIT) {
-            mask->data.PS_TYPE_IMAGE_MASK_DATA[yo][ix] |= crMask;
-        }
-    }
-    for (int ix = xo + 1; ix < pixels->numCols; ix++) {
-        float SN = pixels->data.F32[yo][ix] / sqrt(variance->data.F32[yo][ix]);
-        if (SN > SN_LIMIT) {
-            mask->data.PS_TYPE_IMAGE_MASK_DATA[yo][ix] |= crMask;
-        }
-    }
-
-    // for each of the neighboring rows, mark the high pixels if they have a marked neighbor
-    // first go up:
-    for (int iy = PS_MIN(yo, mask->numRows-2); iy >= 0; iy--) {
-        // mark the pixels in this row to the left, then the right
-        for (int ix = 0; ix < pixels->numCols; ix++) {
-            float SN = pixels->data.F32[iy][ix] / sqrt(variance->data.F32[iy][ix]);
-            if (SN < SN_LIMIT) continue;
-
-            bool valid = false;
-            valid |= (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix] & crMask);
-            valid |= (ix > 0) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix-1] & crMask) : 0;
-            valid |= (ix <= mask->numCols) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix+1] & crMask) : 0;
-
-            if (!valid) continue;
-            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
-        }
-    }
-    // next go down:
-    for (int iy = PS_MIN(yo+1, mask->numRows-1); iy < pixels->numRows; iy++) {
-        // mark the pixels in this row to the left, then the right
-        for (int ix = 0; ix < pixels->numCols; ix++) {
-            float SN = pixels->data.F32[iy][ix] / sqrt(variance->data.F32[iy][ix]);
-            if (SN < SN_LIMIT) continue;
-
-            bool valid = false;
-            valid |= (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix] & crMask);
-            valid |= (ix > 0) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix-1] & crMask) : 0;
-            valid |= (ix <= mask->numCols) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix+1] & crMask) : 0;
-
-            if (!valid) continue;
-            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
-        }
-    }
-    return true;
-}
-
-// given the PSF ellipse parameters, navigate around the 1sigma contour, return the total
-// deviation in sigmas.  This is measured on the residual image - should we ignore negative
-// deviations?  NOTE: This function was an early attempt to classify extended objects, and is
-// no longer used by psphot.
-float psphotModelContour(const psImage *image, const psImage *variance, const psImage *mask,
-                         psImageMaskType maskVal, const pmModel *model, float Ro)
-{
-    psF32 *PAR = model->params->data.F32; // Model parameters
-    float sxx = PAR[PM_PAR_SXX], sxy = PAR[PM_PAR_SXY], syy = PAR[PM_PAR_SYY]; // Ellipse parameters
-
-    // We treat the contour as an ellipse:
-    // Ro = (x / SXX)^2 + (y / SYY)^2 + x y SXY
-    // y^2 (1/SYY^2) + y (x SXY) + (x / SXX)^2 - Ro = 0;
-    // This is a quadratic, Ay^2 + By + C with A = 1/SYY^2, B = x*SXY, C = (x / SXX)^2 - Ro
-    // The solution is y = [-B +/- sqrt (B^2 - 4 A C)] / [2 A], so:
-    // y = [-(x SXY) +/- sqrt ((x SXY)^2 - 4 (1/SYY^2) ((x/SXX)^2 - Ro))] * [SYY^2 / 2]
-
-    // min/max value of x is where B^2 - 4AC = 0; solve this for x
-    float Q = Ro * PS_SQR(sxx) / (1.0 - PS_SQR(sxx * syy * sxy) / 4.0);
-    if (Q < 0.0) {
-        // ellipse is imaginary
-        return NAN;
-    }
-
-    int radius = sqrtf(Q) + 0.5;        // Radius of ellipse
-    int nPts = 0;                       // Number of points in ellipse
-    float nSigma = 0.0;                 //
-
-    for (int x = -radius; x <= radius; x++) {
-        // Polynomial coefficients
-        // XXX Should we be using the centre of the pixel as x or x+0.5?
-        float A = PS_SQR (1.0 / syy);
-        float B = x * sxy;
-        float C = PS_SQR (x / sxx) - Ro;
-        float T = PS_SQR(B) - 4*A*C;
-        if (T < 0.0) {
-            continue;
-        }
-
-        // y position in source frame
-        float yP = (-B + sqrt (T)) / (2.0 * A);
-        float yM = (-B - sqrt (T)) / (2.0 * A);
-
-        // Get the closest pixel positions (image frame)
-        int xPix  = x  + PAR[PM_PAR_XPOS] - image->col0 + 0.5;
-        int yPixM = yM + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
-        int yPixP = yP + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
-
-        if (xPix < 0 || xPix >= image->numCols) {
-            continue;
-        }
-
-        if (yPixM >= 0 && yPixM < image->numRows &&
-            !(mask && (mask->data.PS_TYPE_IMAGE_MASK_DATA[yPixM][xPix] & maskVal))) {
-            float dSigma = image->data.F32[yPixM][xPix] / sqrtf(variance->data.F32[yPixM][xPix]);
-            nSigma += dSigma;
-            nPts++;
-        }
-
-        if (yPixM == yPixP) {
-            continue;
-        }
-
-        if (yPixP >= 0 && yPixP < image->numRows &&
-            !(mask && (mask->data.PS_TYPE_IMAGE_MASK_DATA[yPixP][xPix] & maskVal))) {
-            float dSigma = image->data.F32[yPixP][xPix] / sqrtf(variance->data.F32[yPixP][xPix]);
-            nSigma += dSigma;
-            nPts++;
-        }
-    }
-    nSigma /= nPts;
-    return nSigma;
-}
-
-// this was an old attempt to identify cosmic rays based on the peak curvature
-bool psphotSourcePeakCurvature (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options) {
-
-    // classify the sources based on the CR test (place this in a function?)
-    // XXX use an internal flag to mark sources which have already been measured
-    for (int i = 0; i < sources->n; i++) {
-        pmSource *source = sources->data[i];
-
-        // skip source if it was already measured
-        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
-            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
-            continue;
-        }
-
-        // source must have been subtracted
-        if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) {
-            source->mode |= PM_SOURCE_MODE_SIZE_SKIPPED;
-            psTrace("psphot", 7, "Not calculating source size since source is not subtracted\n");
-            continue;
-        }
-
-        psF32 **resid  = source->pixels->data.F32;
-        psF32 **variance = source->variance->data.F32;
-        psImageMaskType **mask = source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
-
-        // Integer position of peak
-        int xPeak = source->peak->xf - source->pixels->col0 + 0.5;
-        int yPeak = source->peak->yf - source->pixels->row0 + 0.5;
-
-        // Skip sources which are too close to a boundary.  These are mostly caught as DEFECT
-        if (xPeak < 1 || xPeak > source->pixels->numCols - 2 ||
-            yPeak < 1 || yPeak > source->pixels->numRows - 2) {
-            psTrace("psphot", 7, "Not calculating crNsigma due to edge\n");
-            continue;
-        }
-
-        // Skip sources with masked pixels.  These are mostly caught as DEFECT
-        bool keep = true;
-        for (int iy = -1; (iy <= +1) && keep; iy++) {
-            for (int ix = -1; (ix <= +1) && keep; ix++) {
-                if (mask[yPeak+iy][xPeak+ix] & options->maskVal) {
-                    keep = false;
-                }
-            }
-        }
-        if (!keep) {
-            psTrace("psphot", 7, "Not calculating crNsigma due to masked pixels\n");
-            continue;
-        }
-
-        // Compare the central pixel with those on either side, for the four possible lines through it.
-
-        // Soften variances (add systematic error)
-        float softening = options->soft * PS_SQR(source->peak->rawFlux); // Softening for variances
-
-        // Across the middle: y = 0
-        float cX = 2*resid[yPeak][xPeak]   - resid[yPeak+0][xPeak-1]  - resid[yPeak+0][xPeak+1];
-        float dcX = 4*variance[yPeak][xPeak] + variance[yPeak+0][xPeak-1] + variance[yPeak+0][xPeak+1];
-        float nX = cX / sqrtf(dcX + softening);
-
-        // Up the centre: x = 0
-        float cY = 2*resid[yPeak][xPeak]   - resid[yPeak-1][xPeak+0]  - resid[yPeak+1][xPeak+0];
-        float dcY = 4*variance[yPeak][xPeak] + variance[yPeak-1][xPeak+0] + variance[yPeak+1][xPeak+0];
-        float nY = cY / sqrtf(dcY + softening);
-
-        // Diagonal: x = y
-        float cL = 2*resid[yPeak][xPeak]   - resid[yPeak-1][xPeak-1]  - resid[yPeak+1][xPeak+1];
-        float dcL = 4*variance[yPeak][xPeak] + variance[yPeak-1][xPeak-1] + variance[yPeak+1][xPeak+1];
-        float nL = cL / sqrtf(dcL + softening);
-
-        // Diagonal: x = - y
-        float cR = 2*resid[yPeak][xPeak]   - resid[yPeak+1][xPeak-1]  - resid[yPeak-1][xPeak+1];
-        float dcR = 4*variance[yPeak][xPeak] + variance[yPeak+1][xPeak-1] + variance[yPeak-1][xPeak+1];
-        float nR = cR / sqrtf(dcR + softening);
-
-        // P(chisq > chisq_obs; Ndof) = gamma_Q (Ndof/2, chisq/2)
-        // Ndof = 4 ? (four measurements, no free parameters)
-        // XXX this value is going to be biased low because of systematic errors.
-        // we need to calibrate it somehow
-        // source->psfProb = gsl_sf_gamma_inc_Q (2, 0.5*chisq);
-
-        // not strictly accurate: overcounts the chisq contribution from the center pixel (by
-        // factor of 4); also biases a bit low if any pixels are masked
-        // XXX I am not sure I want to keep this value...
-        source->psfChisq = PS_SQR(nX) + PS_SQR(nY) + PS_SQR(nL) + PS_SQR(nR);
-
-        float fCR = 0.0;
-        int nCR = 0;
-        if (nX > 0.0) {
-            fCR += nX;
-            nCR ++;
-        }
-        if (nY > 0.0) {
-            fCR += nY;
-            nCR ++;
-        }
-        if (nL > 0.0) {
-            fCR += nL;
-            nCR ++;
-        }
-        if (nR > 0.0) {
-            fCR += nR;
-            nCR ++;
-        }
-        source->crNsigma  = (nCR > 0)  ? fCR / nCR : 0.0;
-        source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
-
-        if (!isfinite(source->crNsigma)) {
-            continue;
-        }
-
-        // this source is thought to be a cosmic ray.  flag the detection and mask the pixels
-        if (source->crNsigma > options->nSigmaCR) {
-            source->mode |= PM_SOURCE_MODE_CR_LIMIT;
-            // XXX still testing... : psphotMaskCosmicRay (readout->mask, source, maskVal, crMask);
-            // XXX acting strange... psphotMaskCosmicRay_Old (source, maskVal, crMask);
-        }
-    }
-
-    // now that we have masked pixels associated with CRs, we can grow the mask
-    if (options->grow > 0) {
-        bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading for psImageConvolveMask
-        psImage *newMask = psImageConvolveMask(NULL, readout->mask, options->crMask, options->crMask, -options->grow, options->grow, -options->grow, options->grow);
-        psImageConvolveSetThreads(oldThreads);
-        if (!newMask) {
-            psError(PS_ERR_UNKNOWN, false, "Unable to grow CR mask");
-            return false;
-        }
-        psFree(readout->mask);
-        readout->mask = newMask;
-    }
-    return true;
-}
-
 float psphotSourceSizeFindThreshold (psVector *value, psVector *mask, int maskValue, float minValue, float maxValue, float delta, float guess, float fraction) {
 
Index: trunk/psphot/src/psphotSourceStats.c
===================================================================
--- trunk/psphot/src/psphotSourceStats.c	(revision 36374)
+++ trunk/psphot/src/psphotSourceStats.c	(revision 36375)
@@ -58,12 +58,4 @@
     pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
     psAssert (detections, "missing detections?");
-
-    // XXX TEST:
-    if (detections->allSources) {
-        psphotMaskCosmicRayFootprintCheck(detections->allSources);
-    }
-    if (detections->newSources) {
-        psphotMaskCosmicRayFootprintCheck(detections->newSources);
-    }
 
     // determine the number of allowed threads
@@ -246,14 +238,4 @@
     psphotVisualShowMoments (sources);
 
-    // clear the mark bits
-    // psImageMaskPixels (readout->mask, "AND", PS_NOT_IMAGE_MASK(markVal));
-
-    if (detections->allSources) {
-        psphotMaskCosmicRayFootprintCheck(detections->allSources);
-    }
-    if (detections->newSources) {
-        psphotMaskCosmicRayFootprintCheck(detections->newSources);
-    }
-
     return true;
 }
@@ -572,4 +554,6 @@
 
             // measure basic source moments (no S/N clipping on input pixels)
+	    // sources with (mode & MODE_EXTERNAL) or (mode2 & MODE2_MATCHED) use the 
+	    // supplied Mx,My value for the centroid (not recalculated)
             status = pmSourceMoments (source, 4*sigma[i], sigma[i], 0.0, 0.0, maskVal);
         }
Index: trunk/psphot/src/psphotStackReadout.c
===================================================================
--- trunk/psphot/src/psphotStackReadout.c	(revision 36374)
+++ trunk/psphot/src/psphotStackReadout.c	(revision 36375)
@@ -124,19 +124,4 @@
 	return psphotReadoutCleanup (config, view, STACK_RAW);
     }
-
-// XXX TEST for background:
-    if (!psphotModelBackground (config, view, STACK_RAW)) {
-	return psphotReadoutCleanup (config, view, STACK_RAW);
-    }
-    if (!psphotSubtractBackground (config, view, STACK_RAW)) {
-	return psphotReadoutCleanup (config, view, STACK_RAW);
-    }
-    if (!psphotModelBackground (config, view, STACK_RAW)) {
-	return psphotReadoutCleanup (config, view, STACK_RAW);
-    }
-    if (!psphotSubtractBackground (config, view, STACK_RAW)) {
-	return psphotReadoutCleanup (config, view, STACK_RAW);
-    }
-// XXX TEST END 
 
 #ifdef MAKE_CHISQ_IMAGE
@@ -319,5 +304,5 @@
     psphotStackObjectsUnifyPosition (objects);
 
-    psphotStackObjectsSelectForAnalysis (config, view, STACK_RAW, objects);
+    // psphotStackObjectsSelectForAnalysis (config, view, STACK_RAW, objects);
 
     // final linear fit. NOTE: if splitLinearFit is true above, this pass will only fit
@@ -346,4 +331,7 @@
     // measure kron fluxes for the matched sources only
     psphotKronIterate(config, view, STACK_RAW, 3);
+
+    // decide which source(s) are to be fitted with the extended source analysis code.
+    psphotChooseAnalysisOptionsByObject (config, view, STACK_RAW, objects);
 
     // measure elliptical apertures, petrosians (objects sorted by S/N)
Index: trunk/psphot/test/tap_psphot_galaxygrid.pro
===================================================================
--- trunk/psphot/test/tap_psphot_galaxygrid.pro	(revision 36374)
+++ trunk/psphot/test/tap_psphot_galaxygrid.pro	(revision 36375)
@@ -30,8 +30,14 @@
 $RefOptions = $RefOptions -nx 3000 -ny 3000
 
+if (not($?PSFMODEL))
+  $PSFMODEL = PS1_V1
+end
+
+macro reset.options
 # options for the simulated images (using the refimage for the stars)
 $FakeOptions = $BaseOptions
 $FakeOptions = $FakeOptions -exptime 30.0
-$FakeOptions = $FakeOptions -D PSF.MODEL PS_MODEL_GAUSS
+# $FakeOptions = $FakeOptions -D PSF.MODEL PS_MODEL_GAUSS
+$FakeOptions = $FakeOptions -D PSF.MODEL PS_MODEL_$PSFMODEL
 $FakeOptions = $FakeOptions -nx 3000 -ny 3000
   
@@ -69,4 +75,7 @@
 $FakeConfig = $FakeConfig -Di GALAXY.GRID.DX 300
 $FakeConfig = $FakeConfig -Di GALAXY.GRID.DY 300
+end
+
+if (not($?FakeConfig)) reset.options
 
 list fwhm 
@@ -75,4 +84,48 @@
  1.2 
  1.5
+end
+
+if (not($?CONVOLVE_NSIGMA)) set CONVOLVE_NSIGMA = 5.0
+
+# generate fake images and run psphot on them
+macro mkexp.devexp.single
+  if ($0 != 5)
+    echo "USAGE: mkexp.devexp.single (basename) (type) (Rmajor) (fwhm)"  
+    break
+  end
+
+  $basename = $1
+  $type = $2
+  $Rmajor = $3
+  $fwhm = $4
+
+  $Aratio = 1.0
+
+  $FakeConfig = -camera SIMTEST
+  $FakeConfig = $FakeConfig -recipe PPSIM STACKTEST.RUN
+  $FakeConfig = $FakeConfig -D PSASTRO:PSASTRO.CATDIR catdir.ref
+  $FakeConfig = $FakeConfig -Db STARS.FAKE F                         ; # only use stars from catdir.ref
+  $FakeConfig = $FakeConfig -Db MATCH.DENSITY F
+  $FakeConfig = $FakeConfig -Db PSF.CONVOLVE T
+  $FakeConfig = $FakeConfig -Db GALAXY.FAKE T                        ; # generate a "realistic" distribution of galaxies
+  $FakeConfig = $FakeConfig -Df GALAXY.MAG 17.0
+  $FakeConfig = $FakeConfig -Df GALAXY.GRID.MAG 14.5
+  $FakeConfig = $FakeConfig -Db GALAXY.GRID T                        ; # generate a grid of galaxies (constant mag)
+  $FakeConfig = $FakeConfig -Df GALAXY.THETA.MIN 0 
+  $FakeConfig = $FakeConfig -Df GALAXY.THETA.MAX 180
+  $FakeConfig = $FakeConfig -Di GALAXY.GRID.DX 300
+  $FakeConfig = $FakeConfig -Di GALAXY.GRID.DY 300
+  $FakeConfig = $FakeConfig -Df GALAXY.INDEX.MIN 1.0
+  $FakeConfig = $FakeConfig -Df GALAXY.INDEX.MAX 1.0
+  $FakeConfig = $FakeConfig -Df CONVOLVE.NSIGMA $CONVOLVE_NSIGMA 
+  $BaseConfig = $FakeConfig
+
+  $FakeConfig = $BaseConfig
+  $FakeConfig = $FakeConfig -Df GALAXY.RMAJOR.MIN $Rmajor
+  $FakeConfig = $FakeConfig -Df GALAXY.RMAJOR.MAX $Rmajor
+  $FakeConfig = $FakeConfig -Df GALAXY.ARATIO.MIN $Aratio
+  $FakeConfig = $FakeConfig -Df GALAXY.ARATIO.MAX $Aratio
+	  
+  mkexp $basename $fwhm $type
 end
 
@@ -272,4 +325,54 @@
   label -x sequence -y "M_out| - M_in|"
   resize 700 320
+
+  # check on magnitude
+  set dI = Iot_s - Iin_s
+  lim -n 4$1 n -1.0 1.0; clear; box; plot n dI
+  label -x sequence -y "I_out| - I_in|"
+  resize 700 320
+end
+
+macro grid.plot.stars
+  if ($0 != 2)
+    echo "USAGE: grid.plot.stars (version)"
+    break
+  end
+  # things to examine: theta, Rmajor, AR
+
+  # go.grid.check.devexp
+
+  # check on position
+  set dX = Xot_s - int(Xin_s) - 0.5
+  set dY = Yot_s - int(Yin_s) - 0.5
+  set ARin = rin_s / Rin_s
+  set dR = Rin_s - Rot_s
+
+  create n 0 dR[]
+  set dRf = dR / Rin_s
+  lim -n 1$1 n -0.5 0.5; clear; box; plot n dRf
+  label -x sequence -y "1 - R_out| / R_in|"
+  resize 700 320
+
+  # check on A.Ratio
+  set ARot = rot_s / Rot_s
+  # lim ARin ARot; clear; box; plot ARin ARot
+  set fAR = ARot / ARin
+  lim -n 2$1 n 0.5 1.5; clear; box; plot n fAR
+  label -x sequence -y "AR_out| / AR_in|"
+  resize 700 320
+
+  # check on magnitude
+  set dM = Mot_s - Min_s
+  lim -n 3$1 n -0.5 0.5; clear; box; plot n dM    
+  label -x sequence -y "M_out| - M_in|"
+  resize 700 320
+
+  lim -n 4$1 n -1.5 1.5; clear; box; plot n dX
+  label -x sequence -y "X_out| - X_in|"
+  resize 700 320
+
+  lim -n 5$1 n -1.5 1.5; clear; box; plot n dY
+  label -x sequence -y "Y_out| - Y_in|"
+  resize 700 320
 end
 
@@ -487,4 +590,6 @@
 end
 
+if (not($?NSIGMA_CONV)) set NSIGMA_CONV = 5.0
+
 # create a realistic distribution of fake stars, GAUSS PSF
 macro fitexp
@@ -507,4 +612,12 @@
   $psphotConfig = $psphotConfig -Db PSPHOT:SAVE.RESID T
   $psphotConfig = $psphotConfig -D  PSPHOT:EXTENDED_SOURCE_MODELS_SELECTION $fitModel
+  $psphotConfig = $psphotConfig -D  PSPHOT:PSF_MODEL PS_MODEL_$PSFMODEL
+
+  $psphotConfig = $psphotConfig -Db PSPHOT:PSF.RESIDUALS F
+  $psphotConfig = $psphotConfig -Db PSPHOT:POISSON.ERRORS.PHOT.LMM F
+  $psphotConfig = $psphotConfig -Db PSPHOT:EXTENDED_SOURCE_FITS_POISSON F
+  $psphotConfig = $psphotConfig -Di PSPHOT:EXT_FIT_ITER 15
+  $psphotConfig = $psphotConfig -Df PSPHOT:PSF_FIT_RADIUS_SCALE 3.75
+  $psphotConfig = $psphotConfig -Df PSPHOT:EXT_FIT_NSIGMA_CONV $NSIGMA_CONV
 
   # ppImage / psphot on the output
@@ -516,7 +629,18 @@
 end
 
+macro cmf.load.reset
+  $fields = X Y M T R r MT I
+  foreach field $fields
+    foreach set in ot
+      delete -q $field\$set\_s
+    end
+  end
+
+  delete -q min_S Min_S
+end
+
 macro cmf.load.concat
   if ($0 != 4)
-    echo "USAGE: cmf.load.concat (dat) (cmf) (type)"
+    echo "USAGE: cmf.load.concat (dat) (cmf) (inType)"
     break
   end
@@ -525,4 +649,20 @@
   read Xin_all 1 Yin_all 2 Fin_all 3 Type 4 Min_all 5 RmajIn_all 7 RminIn_all 8 ThetaIn_all 9 IndexIn_all 10
 
+  $TYPE_S = 83
+  $TYPE_D = 68
+  $TYPE_E = 69
+  if ("$3" == "SERSIC")
+    $InType = $TYPE_S
+  end
+  if ("$3" == "DEV")
+    $InType = $TYPE_D
+    set IndexIn_all = 0.125 + zero(Xin_all)
+  end
+  if ("$3" == "EXP")
+    $InType = $TYPE_E
+    set IndexIn_all = 0.5 + zero(Xin_all)
+  end
+
+  # select only the galaxies 
   subset Xin = Xin_all if (Type == 1)
   subset Yin = Yin_all if (Type == 1)
@@ -539,26 +679,15 @@
   subset IndexIn = IndexIn_all if (Type == 1)
 
-  $TYPE_S = 83
-  $TYPE_D = 68
-  $TYPE_E = 69
-  if ("$3" == "SERSIC")
-    $InType = $TYPE_S
-  end
-  if ("$3" == "DEV")
-    $InType = $TYPE_D
-  end
-  if ("$3" == "EXP")
-    $InType = $TYPE_E
-  end
-
   data $2
 
   break -auto off
+  output -err /dev/null
   read -fits Chip.xfit X_EXT Y_EXT EXT_INST_MAG EXT_WIDTH_MAJ EXT_WIDTH_MIN EXT_THETA MODEL_TYPE EXT_PAR_07
   $reread = not($STATUS)
+  output -err stderr
   break -auto on
   if ($reread)
     read -fits Chip.xfit X_EXT Y_EXT EXT_INST_MAG EXT_WIDTH_MAJ EXT_WIDTH_MIN EXT_THETA MODEL_TYPE 
-    set EXT_PAR_07 = (MODEL_TYPE:9 == 68)*4 + (MODEL_TYPE:9 == 69)
+    set EXT_PAR_07 = (MODEL_TYPE:9 == 68)*0.125 + (MODEL_TYPE:9 == 69)*0.5
   end
 
@@ -589,11 +718,7 @@
   reindex rin_m = RminIn using index2
   
-  if ("$3" == "SERSIC")
-    reindex Iot_m = EXT_PAR_07 using index1
-    reindex Iin_m = IndexIn using index2
-   $fields = X Y M T R r MT I
-  else
-   $fields = X Y M T R r MT
-  end
+  reindex Iot_m = EXT_PAR_07 using index1
+  reindex Iin_m = IndexIn using index2
+  $fields = X Y M T R r MT I
   
   foreach field $fields
@@ -605,4 +730,57 @@
   concat min min_S
   concat Min Min_S
+end
+
+macro cmf.load.stars.concat
+  if ($0 != 3)
+    echo "USAGE: cmf.load.concat (dat) (cmf)"
+    break
+  end
+
+  data $1
+  read Xin_all 1 Yin_all 2 Type 4 Min_all 5 RmajIn_all 7 RminIn_all 8 ThetaIn_all 9 IndexIn_all 10
+
+  subset Xin     = Xin_all     if (Type == 0)
+  subset Yin     = Yin_all     if (Type == 0)
+  subset Min     = Min_all     if (Type == 0)
+  subset RmajIn  = RmajIn_all  if (Type == 0)
+  subset RminIn  = RminIn_all  if (Type == 0)
+  subset IndexIn = IndexIn_all if (Type == 0)
+  subset Tin_rad = ThetaIn_all if (Type == 0)
+  set Tin = Tin_rad * 180 / 3.14159265
+
+  data $2
+
+  break -auto off
+  read -fits Chip.psf X_PSF Y_PSF PSF_INST_MAG PSF_MAJOR PSF_MINOR PSF_THETA
+  set PSF_THETA_ALT = PSF_THETA * (PSF_THETA >= 0.0) + (PSF_THETA + 3.14159265) * (PSF_THETA < 0.0)
+  set PSF_THETA = PSF_THETA_ALT * 180 / 3.14159265
+  
+  match2d X_PSF Y_PSF Xin Yin 1.0 -index1 index1 -index2 index2
+
+  reindex Xot_m = X_PSF using index1
+  reindex Yot_m = Y_PSF using index1
+
+  reindex Xin_m = Xin using index2
+  reindex Yin_m = Yin using index2
+
+  reindex Mot_m = PSF_INST_MAG using index1
+  reindex Tot_m = PSF_THETA using index1
+
+  reindex Min_m = Min using index2
+  reindex Tin_m = Tin using index2
+
+  reindex Rot_m = PSF_MAJOR using index1
+  reindex rot_m = PSF_MINOR using index1
+
+  reindex Rin_m = RmajIn using index2
+  reindex rin_m = RminIn using index2
+  
+  $fields = X Y M T R r
+  foreach field $fields
+    foreach set in ot
+      concat $field\$set\_m $field\$set\_s
+    end
+  end
 end
 
@@ -1078,6 +1256,6 @@
 
 macro load.normdata
-  if ($0 != 2)
-    echo "USAGE: load.normdata (file)"
+  if ($0 != 3)
+    echo "USAGE: load.normdata (file) [clear/noclear]"
     break
   end
@@ -1090,5 +1268,8 @@
   set dm = mg - Mg
   set n = ramp(dm)
-  lim n dm; clear; box; plot n dm
+  if ("$2" == "clear")
+    lim n dm; clear; box; 
+  end
+  plot n dm
 end
 
@@ -1097,2 +1278,168 @@
   exit 0
 end
+
+# note that t1 = original confi
+# t2 = no residuals
+# t3 = const weight PSF fit
+# t4 = const weight galaxy fits
+
+macro run.radius.loop
+  local radius Nrun 
+
+  if (1)
+    $Nrun = 0
+    foreach radius 1.0 1.5 2.0 2.5 3.0 4.0 6.0 8.0
+      mkexp.devexp.single tests.20131120/testrad.ps1v1.exp.$Nrun EXP $radius 1.0
+      fitexp tests.20131120/testrad.ps1v1.exp.$Nrun tests.20131120/testrad.ps1v1.exp.$Nrun.t4b EXP_CONV
+      $Nrun ++
+    end
+  end
+
+  $Nrun = 0
+  foreach radius 1.0 1.5 2.0 2.5 3.0 4.0 6.0 8.0
+    cmf.load.concat tests.20131120/testrad.ps1v1.exp.$Nrun.dat tests.20131120/testrad.ps1v1.exp.$Nrun.t4b.cmf EXP
+    echo -no-return $radius ""
+    check.fit tests.20131120/testrad.ps1v1.exp.$Nrun.dat tests.20131120/testrad.ps1v1.exp.$Nrun.t4b.cmf EXP
+    $Nrun ++
+  end
+
+  grid.plots.devexp a
+end
+
+macro check.radius.loop
+  if ($0 != 2)
+    echo "USAGE: check.radius.loop (ext)"
+    break
+  end
+
+  # cmf.load.reset
+
+  local radius Nrun 
+
+  $Nrun = 0
+  foreach radius 1.0 1.5 2.0 2.5 3.0 4.0 6.0 8.0
+    echo -no-return $radius ""
+    check.fit tests.20131120/testrad.ps1v1.exp.$Nrun.dat tests.20131120/testrad.ps1v1.exp.$Nrun.$1.cmf EXP
+    $Nrun ++
+  end
+
+  grid.plots.devexp a
+end
+
+macro check.fit
+  if ($0 != 4)
+    echo "USAGE: check.fit (dat) (cmf) (type)"
+    break
+  end
+
+  cmf.load.concat $1 $2 $3
+  set dM_m = Mot_m - Min_m
+  vstat -q dM_m
+  $mag_off = $MEDIAN
+  set dR_m = Rot_m - Rin_m
+  vstat -q dR_m
+  $rad_off = $MEDIAN
+  echo $mag_off $rad_off
+end
+
+macro make.circles
+
+  delete Xo Yo Ro
+
+  for ix 0 16
+    for iy $ix 16
+      $r2 = $ix^2 + $iy^2
+      if ($r2 > 15^2) continue
+      concat $ix Xo
+      concat $iy Yo
+      concat $r2 Ro
+    end
+  end
+
+  sort Ro Xo Yo
+  $Rold = -1
+  $N = -1
+  for i 0 Ro[]
+    if (Ro[$i] != $Rold)
+      $N ++
+      $Rold = Ro[$i]
+    end
+    if ((Xo[$i] == 0) && (Yo[$i] == 0))
+      fprintf "// center is 0,0"
+      continue
+    end
+    if (Xo[$i] == 0)
+      fprintf "ADD_AXIS (%3d, %2d)     // r^2 = %3d" $N Yo[$i] Ro[$i]
+      continue
+    end
+    if (Xo[$i] == Yo[$i])
+      fprintf "ADD_DIAG (%3d, %2d)     // r^2 = %3d" $N Xo[$i] Ro[$i]
+      continue
+    end
+
+    fprintf "ADD_RAND (%3d, %2d, %2d) // r^2 = %3d" $N Xo[$i] Yo[$i] Ro[$i]
+ end
+end
+
+# EXP : tests.20131120/test.nsig
+# DEV : tests.20131120/test.dev
+
+# run.convolve.loop tests.20131120/test.nsig tests.20131120/test.nsig   EXP EXP_CONV
+# run.convolve.loop tests.20131120/test.dev  tests.20131120/test.dev    DEV DEV_CONV
+# run.convolve.loop tests.20131120/test.nsig tests.20131120/test.serexp EXP SER_CONV
+# run.convolve.loop tests.20131120/test.dev  tests.20131120/test.serdev DEV SER_CONV
+
+macro run.convolve.loop
+  if ($0 != 5)
+    echo "run.convolve.loop (inName) (fitName) (inType) (fitType)"
+    break
+  end
+
+  local radius
+  $radius = 3.0
+  cmf.load.reset
+
+  # foreach Cin 3 5 7 9 11
+  foreach Cin 11
+    $CONVOLVE_NSIGMA = $Cin
+    sprintf nameIn %s.%02d $1 $Cin
+    mkexp.devexp.single $nameIn $3 $radius 1.0
+    foreach Cot 3 5 7 9 11
+      $NSIGMA_CONV = $Cot
+      sprintf nameOt %s.%02d.%02d $2 $Cin $Cot
+      fitexp $nameIn $nameOt $4
+    end
+  end
+
+  # foreach Cin 3 5 7 9 11
+  foreach Cin 11
+    sprintf nameIn %s.%02d $1 $Cin
+    foreach Cot 3 5 7 9 11
+      sprintf nameOt %s.%02d.%02d $2 $Cin $Cot
+      check.fit $nameIn.dat $nameOt.cmf 
+    end
+  end
+  grid.plots.devexp a
+end
+
+macro check.convolve.loop
+  if ($0 != 4)
+    echo "run.convolve.loop (inName) (fitName) (inType)"
+    break
+  end
+
+  local radius
+  $radius = 3.0
+  cmf.load.reset
+
+  # foreach Cin 3 5 7 9 11
+  foreach Cin 11
+    sprintf nameIn %s.%02d $1 $Cin
+    foreach Cot 3 5 7 9 11
+      sprintf nameOt %s.%02d.%02d $2 $Cin $Cot
+      echo -no-return $Cin $Cot " "
+      check.fit $nameIn.dat $nameOt.cmf $3
+    end
+  end
+  grid.plots.devexp a
+end
