Index: /branches/eam_branches/eam_branch_20080229/psphot/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/.cvsignore	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/.cvsignore	(revision 22147)
@@ -0,0 +1,19 @@
+bin
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+libtool
+ltmain.sh
+stamp-h1
+config.sub
+psphot.pc
+psphot-config
Index: /branches/eam_branches/eam_branch_20080229/psphot/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/Makefile.am	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/Makefile.am	(revision 22147)
@@ -0,0 +1,12 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+bin_SCRIPTS = psphot-config 
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psphot.pc
+
+EXTRA_DIST = \
+	psphot-config.in \
+	psphot.pc.in \
+	autogen.sh
Index: /branches/eam_branches/eam_branch_20080229/psphot/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/autogen.sh	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/autogen.sh	(revision 22147)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=psphot
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080229/psphot/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/configure.ac	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/configure.ac	(revision 22147)
@@ -0,0 +1,216 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([psphot], [0.9.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------------ kapa,libkapa options -------------------------
+dnl -- libkapa implies the requirement for libpng, libjpeg as well --
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+dnl test for command-line options: use ohana-config if not supplied
+KAPA_CFLAGS_CONFIG="true"
+KAPA_LIBS_CONFIG="true"
+AC_ARG_WITH(kapa,
+[AS_HELP_STRING(--with-kapa=DIR,Specify location of libkapa)],
+[KAPA_CFLAGS="-I$withval/include" KAPA_LIBS="-L$withval/lib" 
+ KAPA_CFLAGS_CONFIG="false"       KAPA_LIBS_CONFIG="false"])
+AC_ARG_WITH(kapa-include,
+[AS_HELP_STRING(--with-kapa-include=DIR,Specify libkapa include directory.)],
+[KAPA_CFLAGS="-I$withval" KAPA_CFLAGS_CONFIG="false"])
+AC_ARG_WITH(kapa-lib,
+[AS_HELP_STRING(--with-kapa-lib=DIR,Specify libkapa library directory.)],
+[KAPA_LIBS="-L$withval" KAPA_LIBS_CONFIG="false"])
+
+echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+echo "KAPA_LIBS_CONFIG: $KAPA_LIBS_CONFIG"
+echo "KAPA_CFLAGS: $KAPA_CFLAGS"
+echo "KAPA_LIBS: $KAPA_LIBS"
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+HAVE_KAPA="true"
+AC_MSG_NOTICE([checking for libkapa])
+if test "$KAPA_CFLAGS_CONFIG" = "true" -o "$KAPA_LIBS_CONFIG" = "true"; then
+  AC_MSG_NOTICE([kapa info supplied by ohana-config])
+  KAPA_CONFIG=`which ohana-config`
+  AC_CHECK_FILE($KAPA_CONFIG,[],
+    [HAVE_KAPA="false"; AC_MSG_WARN([libkapa is not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location])])
+  
+  echo "HAVE_KAPA: $HAVE_KAPA"
+  echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_CFLAGS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa cflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa cflags])
+   KAPA_CFLAGS="`${KAPA_CONFIG} --cflags`"
+   AC_MSG_RESULT([${KAPA_CFLAGS}])
+  fi
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_LIBS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa ldflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa ldflags])
+   KAPA_LIBS="`${KAPA_CONFIG} --libs` -lX11"
+   AC_MSG_RESULT([${KAPA_LIBS}])
+  fi
+fi
+
+if test "$HAVE_KAPA" = "true" ; then
+ AC_MSG_NOTICE([libkapa supplied])
+ PSPHOT_CFLAGS="${PSPHOT_CFLAGS} ${KAPA_CFLAGS}"
+ PSPHOT_LIBS="${PSPHOT_LIBS} ${KAPA_LIBS}"
+else
+ AC_MSG_NOTICE([libkapa ignored])
+fi
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+dnl HAVE_KAPA=true
+dnl AC_CHECK_HEADERS([kapa.h],
+dnl  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $KAPA_CFLAGS" AC_SUBST(KAPA_CFLAGS)],
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])]
+dnl )
+dnl AC_CHECK_LIB(kapa,KapaInitGraph,
+dnl  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],  
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])],[-lm]
+dnl )
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libjpeg options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(jpeg,
+[AS_HELP_STRING(--with-jpeg=DIR,Specify location of libjpeg.)],
+[JPEG_CFLAGS="-I$withval/include"
+ JPEG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(jpeg-include,
+[AS_HELP_STRING(--with-jpeg-include=DIR,Specify libjpeg include directory.)],
+[JPEG_CFLAGS="-I$withval"])
+AC_ARG_WITH(jpeg-lib,
+[AS_HELP_STRING(--with-jpeg-lib=DIR,Specify libjpeg library directory.)],
+[JPEG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${JPEG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${JPEG_LDFLAGS}"
+
+AC_CHECK_HEADERS([jpeglib.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $JPEG_CFLAGS" AC_SUBST(JPEG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg headers not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg library not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libpng options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(png,
+[AS_HELP_STRING(--with-png=DIR,Specify location of libpng.)],
+[PNG_CFLAGS="-I$withval/include"
+ PNG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(png-include,
+[AS_HELP_STRING(--with-png-include=DIR,Specify libpng include directory.)],
+[PNG_CFLAGS="-I$withval"])
+AC_ARG_WITH(png-lib,
+[AS_HELP_STRING(--with-png-lib=DIR,Specify libpng library directory.)],
+[PNG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${PNG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${PNG_LDFLAGS}"
+
+AC_CHECK_HEADERS([png.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $PNG_CFLAGS" AC_SUBST(PNG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng headers not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+AC_CHECK_LIB(png,png_init_io,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $PNG_LDFLAGS -lpng"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng library not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ use kapa or not? ---------------------
+
+if test "$HAVE_KAPA" == "true" ; then
+  AC_MSG_RESULT([including plotting functions])
+  AC_DEFINE([HAVE_KAPA],[1],[enable use of libkapa])
+else
+  AC_MSG_RESULT([skipping plotting functions])
+  AC_DEFINE([HAVE_KAPA],[0],[disable use of libkapa])
+fi
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+
+CFLAGS="${CFLAGS=} -Wall -Werror"
+echo "PSPHOT_CFLAGS: $PSPHOT_CFLAGS"
+echo "PSPHOT_LIBS: $PSPHOT_LIBS"
+
+AC_SUBST([PSPHOT_CFLAGS])
+AC_SUBST([PSPHOT_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  psphot-config
+  psphot.pc
+])
+
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/2Dmodels.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/2Dmodels.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/2Dmodels.txt	(revision 22147)
@@ -0,0 +1,29 @@
+
+I am testing a new concept for the 2D representation of various values
+measured by psphot.  I have been using 2D polynomials to fit the
+variations of, eg, the psf parameters as a function of position in the
+images.  this has been somewhat successful, but it has also been
+fraught with a number of problems.  the worst issue has been the fact
+that the polynomial form is not physically motivated.  in some cases,
+eg the variation of the psf shape parameters, the true variations for
+real distributions are not well represented by a polynomial.  I have
+done some ad hoc tricks to get the functional form to look roughly
+like a polynomial, but this is really quite a hack.  the other problem
+is that it is difficult to decide if the polynomial does an acceptable
+job representing the variations.
+
+My new concept for modeling 2D variations in some parameter is to
+define a low-resolution 2D image to represent the variation.  The
+advantage is that the image may be define with whatever resolution may
+be sampled by the input data, and the functional form need not be
+constrained by the polynomial basis function.  
+
+** is this a poor substitute for using a chebychev polynomial as a
+   better basis function?
+
+psphot parameters which vary by position:
+
+pmPSF.params 
+pmPSF.ApTrend
+pmPSF.FluxScale (flux given normalization of 1.0)
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/config.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/config.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/config.txt	(revision 22147)
@@ -0,0 +1,56 @@
+
+# input data options
+# IMAGE             STR    file.fits
+# MASK_IMAGE	    STR    mask.fits
+# WEIGHT_IMAGE	    STR	   weight.fits
+
+# output data options
+OUTPUT_FILE	    STR	   output.smp    # output object file
+OUTPUT_MODE	    STR	   TEXT          # output mode: TEXT, OBJ, SX, CMP, CMF 
+# RESID_IMAGE	    STR	   resid.fits    # output residual image
+
+# image noise parameters
+RDNOISE             STR  HD:RDNOISE      # read-noise in electrons
+GAIN                STR  HD:GAIN         # electrons / DN
+
+# masking parameters
+XMIN                F32  32        	 # minimum valid x-coord
+XMAX                F32   0        	 # maximum valid x-coord
+YMIN                F32   1        	 # minimum valid y-coord
+YMAX                F32   0        	 # maximum valid y-coord
+SATURATION          F32  50000     	 # saturation level on this chip
+
+# image statistics parameters
+IMSTATS_NPIX        S32  100000    	 # number of pixels to use for sky estimate:
+
+# peak finding 
+PEAKS_SMOOTH_SIGMA  F32  1.0       	 # smoothing kernel sigma in pixels
+PEAKS_SMOOTH_NSIGMA F32  3.0   	   	 # smoothing kernel width in sigmas
+PEAKS_NSIGMA_LIMIT  F32  10.0  	   	 # peak significance threshold
+
+# basic object statistics
+SKY_INNER_RADIUS    F32  15		 # square annulus for local sky measurement
+SKY_OUTER_RADIUS    F32  25		 # square annulus for local sky measurement
+PSF_MOMENTS_RADIUS  F32  5
+PSF_SN_LIM          F32  100
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+#PSF_MODEL           STR  PS_MODEL_PGAUSS
+#PSF_MODEL           STR  PS_MODEL_GAUSS
+PSF_FIT_RADIUS      F32  25		 # fitting radius for test PSF model
+
+# PSF model parameters : apply the PSF model
+PSF_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+PSF_FIT_PADDING      F32  5              # extra annulus to use for fit 
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MIN_SN           F32  2.0		 # reject objects below this significance
+PSF_MAX_CHI          F32  10.0		 # reject objects worse that this
+
+# Galaxy model parameters
+GAL_MODEL            STR  PS_MODEL_SGAUSS
+GAL_MIN_SN           F32  3
+GAL_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+GAL_FIT_PADDING      F32  5              # extra annulus to use for fit 
+GAL_MOMENTS_RADIUS   F32  9
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/notes.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/notes.txt	(revision 22147)
@@ -0,0 +1,638 @@
+
+2007.08.17
+
+ I am working on a number of cleanup / fixes.  I have made an overhaul
+ of the pmModel APIs, adding function pointers in the pmModel
+ structure to the class-specific utility functions (eg,
+ pmModel->modelFunc is the actual function which is evaluated). 
+
+ TO DO:
+
+ * update pmSourceFitSet to be able to include more than one model
+   type (currently it assumes the sources are all, eg, PSFs).  This is
+   now needed because the old implementation used the function lookups
+   which have been dropped.
+
+ * define a generic API set to handle 2D modelling of a scalar using
+   either polynomials (as the pmPSF code currently does) or an
+   image-based representation (as the psphot sky model currently
+   does).  
+
+   The image-based representation can automatically step down from NxM
+   super pixels to a smaller number based on the density of
+   measurements.  
+
+   Include ways to smooth and regularize the output result.
+
+   This mechanism can be applied to: psf parameters, aperture
+   residual, psf peak-to-flux variations, the psphot background
+   representation.
+
+ * generate and store the output radial profile for objects
+
+ * finish testing and incorportate the CR / EXT measurements
+
+ * adjustments to pixel center based on second derivatives : needed
+   for the sersic models.
+
+ * adjustments to pixel flux for extreme values (r ~ 0) : needed for
+   the sersic models.
+
+ * on psf stars : fall back on a Gaussian.
+
+ * OPTIMIZATIONS !!
+
+ * drop the model sky element : should only be in source->sky,dsky
+
+2006.11.16
+
+ensemble:
+
+  * first block: 
+    * select sources of interest / skip
+    * generate a guess model
+    * re-measure moments for saturated stars (MOVE)
+    * update peak position (MOVE to peaks)
+    * set the object radius 
+    * build temporary source copies (including pixels, mask, weight)
+
+  * second block:
+    * measure the cross-products and weights
+    * add to the sparse matrix
+
+  * build full-image mask
+  * measure coord weights
+  * build order elements
+
+  * solve for source amplitudes
+  * update models
+
+  * create psSparseBorder to solve matrix equations which have a large
+    sparse region, with a completely filled border of a smaller number
+    of rows.
+
+  * trying to modify psphotEnsemble to (1) be more general (not just
+    PSFs) and (2) fit for the sky offset.
+
+    - how is the radius for each source set?
+    
+
+2006.11.11
+
+  I am trying to define a complete set of tests to validate the
+  measurement of the photometry compared with the simulated images.
+  Part of my confusion is controlling exactly which corrections are
+  applied to the output magnitudes.  Here is the complete list:
+
+  psfMag_out = psfMag_meas + ApTrend(x,y,0.0,0.0)
+  apMag_out = apMag_meas + growth(radius,refRadius) + f(skyBias,skySat)
+
+  these corrections are modified by the following config settings:
+
+  IGNORE_GROWTH (sets all growth magnitudes to the fitMag value,
+  effectively turning off the growth correction)
+
+  APTREND : set the level of correction
+    NONE     : no correction is fitted
+    CONSTANT : just a single constant (applied to get psfMag_out)
+    SKYBIAS  : the skybias term is fitted (constant applied to psfMag,
+               slope applied apMag)
+  
+2006.11.08
+
+  I have been testing the pmSourceMagnitudes interpolation of the
+  source position when measuing the aperture magnitudes.  I built a
+  test suite for pmSourceMagnitudes and pmGrowthCurve.  even with
+  bicubic interpolation, there are noticable errors for small
+  apertures (ie, small compared to the PSF sigma).  I used a gaussian
+  PSF to make the tests.  it seems that, if the radius is less than 2
+  sigma or so, the interpolated aperture mag is in error by as much as
+  12mmag.  The size of this effect must depend on the smoothing
+  algorithm, the shape of the PSF, the size of the PSF relative to the
+  pixels, and the size of the subpixel interpolation.  I believe the
+  effect is a result of the smoothing introduced by the interpolation:
+  the smoothing pushes the flux in the inner portions of the PSF down
+  and enhances the flux in the outer portions.  thus, there is
+  actually a turn over in the error: at very large values, the error
+  is very small because the aperture contains all of the flux, and the
+  interpolation has little effect.  as the radius shrinks, the error
+  grows (in the sense of M_raw - M_interpolated; ie, the flux in the
+  interpolated source is too large).  around 2-3sigma, the error turns
+  over and becomes negative (too little flux in the interpolated
+  source). 
+
+  The sigmas I used for these tests (1.0, 1.5, 2.0 pix) are
+  appropriate for the range we expect to see from PS1, or any
+  well-sampled detector.  Thus, even though the effect is probably
+  less important for poorer seeing, we have to be careful to avoid it
+  in our Pan-STARRS analysis.
+
+  Currently, the minimum aperture is defined only by the config
+  variable PSF_FIT_PAD, which really implies a padding.  My proposal
+  is to require the minimum aperture to be a number of sigma (an
+  additional parameter).  Then, the formula for the radius would be
+  something like:
+
+  MAX (modelRadiusPSF (PSF_FIT_NSIGMA), PSF_FIT_RADIUS_NSIG) +
+  PSF_FIT_PAD
+
+  with PSF_FIT_RADIUS_NSIG (choose a better name!) set to something
+  like 2 or 3.
+
+2006.11.06
+
+  I have added the aperture interpolation as a component of the
+  pmSourceMagnitudes function.  
+
+  I am trying to make things a bit more organized, with more
+  consistent APIs. 
+
+  * changed pmPSFtry to use the pmSourceMagnitudes API
+  * changed pmPSFtry to use new pmSource entries for each try (saving
+  * the pmModels generated)
+
+  other notes:
+  * moved psphotGrowthCurve -> pmGrowthCurveGenerate 
+
+  things I know need to be added / fixed:
+  * measure bicube fit to peak from smoothed image
+  * use psphotEnsemble fit to re-measure normalization?
+  * extended object statistical measures 
+
+2006.11.02
+
+  Looking into the growth curve problem.  RHL sent his recipe for
+  correcting to true circular apertures.  I've done a test fix of
+  interpolating the stellar image to the desired center.  it looks
+  like bilinear interpolation is insufficient (about 8% error for 2
+  pixel radius aperture).  Unless I've got the formula wrong, the
+  effective smoothing of the gaussian is moving flux from the center
+  out and making small apertures in error that depends on the offset.
+  Bicubic could be enough to clean this up?  
+
+  I think the current code may need to be careful modified to include
+  the interpolation for every aperture measurement.  there are paired
+  'pmModelAdd' and pmModelSub steps to add and remove the model flux.
+  I need to be careful about how the mask affects these steps.
+
+  I also need to be careful in interpreting the test results: since
+  the simulated images create objects by filling in a pixel and then
+  smoothing, all objects have centroids which are exactly 0.5 pixel
+  offsets.  These all then have a fixed growth correction error, which
+  fortunately happens to be substantial (because of a 0.5 pixel offset
+  in the growth curve reference object!)
+
+  I need to double check the 0.5 pixel error issues.  There are
+  problems with the pslib version of psImageShift and
+  psImageInterpolate (or at least inconsistencies).  the pmModelAdd
+  and Sub functions and the centroid functions are probably also
+  inconsistent.
+
+2006.11.01
+
+  Trying to understand low-level errors in psphot using simulated
+  data.  the main issue I am concerned with is the bias RHL is seeing
+  in SDSS-psphot comparisons.  But there are other issues that are
+  creeping in and probably point at logical errors in the code.
+
+  * in pmPSFtry, I measure the first pass on the aperture residual (to
+    choose between PSF model options, if that is being tested).  This
+    analysis currently uses a polynomial fit (1st order) for the offset
+    and the sky bias (using r^2/flux as an independent variable).
+
+    Notes:
+
+    - is the psf-fit error distribution reasonable?  perhaps a little
+      on the high side: at 10000 DN, noise is 0.0138, should be
+      0.0107.
+
+    - scatter of ap-fit is surprisingly high.  M_input - M_fit has a
+      median chisq of 2 (mean of 5), while M_input - M_ap has a median
+      chisq of 125 (mean of 380).  This is using a radius of 15.  It
+      cleans up hugely when I go to a radius of 7 and even more so
+      with radius = 4. (In this case, the PSF sigma is 1.5, so 4 is
+      well matched and 7 is only slightly large).
+
+      * make the initial PSF fitting radius and aperture radius
+	  different?  
+      * make the initial PSF fitting radius a function of the measured
+	  PSF sigma?
+
+    - I have been fitting ap-fit mag without weights; the resulting
+      ap-fit errors are highly over-estimated since the ap-fit scatter
+      is too high.  I have added code to carry the weights (error from
+      the fit only at the moment, ie, not the ap error as well).  This
+      is good, but not as important if the radius is chosen well above.
+
+  * a bias in the psphotEnsemble results depending on the sky level
+    but not in the PSF model fits?  turning on/off CONST_PHOT_WT has
+    an equivalent effect, but is quite small.  why is one sensitive to
+    the sky and the other is not?  
+
+  * the error measured by psphotEnsemble was wrong (and inconsistent
+    for a given weighting situation): fixed the def of the error.
+
+  * still trying to understand the corrections being made.  running a
+    few tests:
+
+    keeping PSF_FIT_RADIUS at 4.0
+
+    t1 : no growth correction,    constant weights
+    t2 :    growth correction,    constant weights
+    t3 : no growth correction, no constant weights
+    t4 :    growth correction, no constant weights
+    t5 :    growth correction,    constant weights, constant ap (4)
+    t6 :    growth correction,    constant weights, integer radius
+    t7 :    growth correction,    constant weights, integer radius
+    (for t7, I've fixed the bug that the growth correction include the
+    min radius).
+
+  * psphotGrowthCurve is messed up : the measurement does not correct
+    for the actual object center, and at small radii this is an
+    important source of noise.
+
+2006.06.28
+
+  I am adding a few minor features.  First up is the ability to break
+  the processing at interesting stages.  These will be:
+
+  - PEAKS: after sources are generated, but before moments are
+    measured
+  - MOMENTS: after moments are measured, before the PSF is measured
+  - PSFMODEL: after the psf model is measured, before sources are
+    fitted in bulk
+  - ENSEMBLE: after the linear ensemble fitting
+  - DEFAULT: complete processing
+
+2006.04.22
+
+  I have updated psphot to work with the cycle 11 release of psLib and
+  psModules.  At this point, the IfA group is now working off of the
+  CVS head rather than from a separate IfA branch of psLib and
+  psModules.  The cycle 11 release of psLib needed a few updates to
+  fix minor problems.  The psModules code needed significant work to
+  fix discrepancies.  The new version of psphot has also been leak
+  checked, at least for basic configurations.  This new version of
+  psphot should be used with the new camera configuration system,
+  which can be found in the ipp/config directory.  Here are the
+  necessary tags:
+
+  psphot: psphot_dev_11_0
+  psModules: rel11_ifa_pre0
+  psLib: rel11_ifa_0
+
+2006.04.16
+
+  Examining some of the throughput issues.  the flux measurement speed
+  is strongly dependent on the interation of the function step size:
+
+  dz = 0.01 : 3.24s for 4400 objects
+  dz = 0.1  : 0.44s for 4400 objects
+
+  there is an increased error in the result with the larger step size,
+  but it is smaller than the phot error.  Still, we could do better
+  with a smarter integration function.
+
+  Including the aperture photometry on all sources, but not the
+  pixWeight, the source magnitudes takes 4.8 sec for 4400 objects.
+
+  Adding the pixWeight increases the time to 7.5 sec for 4400 objects
+
+2006.02.12
+
+  Last week, I made a new tag (psphot_dev_08) after finishing the work
+  to clean memory leaks and to modify psphot to work with Paul's
+  ppImage infrastructure.  
+
+  I have added a new background model function which generates a
+  median map based on Robert's suggestion of a) half-step windows and
+  b) linear interpolation.
+
+  Now, I am working to remove the constant sky level concepts and make
+  every relevant test use the signal/noise ratio as the trigger.  This
+  has impacts on the peak detections, the threshold for blends, and
+  the threshold for choosing pixels in the fitting routines.
+
+2005.12.23
+
+  Improvements in psphot as of psphot_dev_07
+
+  - updated to work with psModule release 9.0
+  - moved model test functions into psphot as optional mode
+  - added pmSourceFitSet : simultaneous model group fitting
+  - added bicubic interpolation to tweak up the peak coordinates
+  - added aperture residual analysis after complete object model & subtraction
+  - added 3D (x,y,rflux) aperture residual fitting
+  - added blend vs blob fitting (BlendFit)
+  - added ensemble PSF fitting, with sparse matrix inversion
+  - improved the flagging / marking of model results
+  - better abstraction for setting fit parameters
+  - various low-level fixes
+  - added PSF output option
+  - added break points
+  - added more optional output formats
+  - fixed FITS table output
+
+  Things still to be added to psphot:
+
+  - better initial background model (NxM median grid?)
+  - include local background model in EnsemblePSF Fit (use sparse matrix)
+  - 2nd pass (after object subtraction) to smooth and detect faint objects
+  - re-add background to object-subtracted image
+  - cleanup function names
+  - push more functions into psLib / psModule
+  - fix extended / galaxy models to improve stability
+  - add mode with input PSF
+  - add mode with input fixed sources (PS_SOURCE_FIXED)
+  - cleanup memory leaks
+
+2005.11.25
+
+  I've updated psphot to work with the current psLib v8, though a
+  number of psLib fixes were needed.  These are pushed under
+  psLib:eam_rel8_b2.  The psphot code which works with that version of
+  psLib is tagged psphot_dev_04.
+
+  I'm working on converting psphot to work with the current release of
+  psModules, which basically includes all of the object functions. 
+
+  - change comments are relative to psModules (vs psphot versions)
+
+  psEllipse.c : OK
+  psEllipse.h : OK
+  pmModelGroup.c : OK (missing TGAUSS entry)
+  pmModelGroup.h : OK (adds many comments)
+
+  pmPSFtry.c : a bunch of formatting weirdness (try-> got line breaks
+	       added pmPSFtryMetric_Alt
+	       fixed up usage of psVectorClipFitPolynomial1D  
+	       fixed up usage of psPolynomial..Alloc  
+
+  pmPSFtry.h : OK (Added extensive comments)
+
+  pmPSF.c : fixed up usage of psPolynomial..Alloc
+
+  pmPSF.h : OK (Added extensive comments)
+
+  pmObjects.c : cleaned up some formatting,
+		fixed usage of psImageCountPixelMask
+
+  pmObjects.h : added apMag and fitMag to pmSource
+		dropped PS_MODEL_name defines
+
+  psLibUtils.[ch] should be dropped from psModules
+  psModulesUtils.[ch] should be dropped from psModules
+
+  Makefile.am : dropped psModulesUtils.[ch], psLibUtils.[ch]
+
+  - I have successfully tested psphot with the psModules.v8 code
+    (eam_rel8_b1 tag).  I only needed to make a few modes in the
+    psModules code.  
+
+2005.11.16
+
+  I have made some fixes to make psphot work with pslib rel8_0.  I
+  need to merge psphot with the rel8_0 version of the psMinimize and
+  pmObjects code.  
+
+2005.09.06
+
+  I have built a working version of PSPhot using my own copy of the
+  pmObjects.[ch] files.  I need to minimize the difference between the
+  v0.7.0, v0.5.0, and my own version of pmObjects.c
+
+  additions to psLib based on the new version:
+  - extended the minimization to include parameter limits
+
+  changes v0.5.0 / v0.7.0 / EAM
+  - changed all ps* to pm*
+  - fixed naming for local static functions (eg modelFree not p_psModelFree)
+  - dropped all hard-coded model names (vs v0.7.0)
+  - using current psMemSetDellocator name
+  - added noise image to pmSource
+  - converted old asserts to new asserts (PS_.._CHECK..)
+  - add return from error in pmFindImagePeaks
+  - allow isItInThisRegion to have NULL region (true)
+  - fixed subImages to correspond to region definition (end is
+    exclusive) in LocalSky and SetPixelCircle
+  - dropped old, redundant code in pmSourceLocalSky
+  - fixed pmSourceMoments use of mask (0 is valid, not 1)
+  - added pmSourceMoments validity tests (numPixels, Sum, centroid shift)
+  - added correction for Sxy (XY/Sum - x*y)
+  - added pmSourcePSFClump function (and pmPSFClump structure)
+  - allow missing sources in pmSourcePSFClump (not all peaks yield
+    sources)
+  - moved pmSourceRoughClass metadata lookups out of inner loop
+  - added saturated pixel test using pmImageCountPixelMask
+  - various redefinitions of initial classes
+  - dropped old, redundant code in pmSourceSetPixelsCircle
+  - mask image set to type psU8 (was psF32)
+  - abstract object model functions for pmSourceModelGuess
+  - converted evalModel to a public function psModelEval, modified params
+  - pmSourceFitModel operates on a specified model
+  - pmSourceFitModel had option to apply/ignore PSF information
+  - pmSourceFitModel calculates yErr
+  - pmSourceFitModel calculates the covariance matrix
+  - added paramMask to pmSourceFitModel 
+  - pmSourceFitModel_EAM uses covar to carry in beta and param
+    limits...
+  - pmSourceFitModel requires solution to stay within image
+  - pmSourceFitModel calculates and saves chisq, nIter, nDOF
+  - pmSourceFitModel uses GaussNewtonDelta for frozen params
+  - added mask to pmSourceAdd, pmSourceSub
+  - model abstractions in pmSourceAdd, pmSourceSub
+  - fixed 'center' option in pmSourceAdd/Sub
+
+  - pmPSF params should be F64
+
+  lingering concerns:
+  - Polynomials and related functions are STILL messed up.  this is a
+    major priority!!!
+  - pmSourceFitModel calculates sqrt(var), psMinimize calculates sq(sqrt(var))
+  - does psImageAlloc zero the image?  if so, drop init routine
+    from pmSourcePSFClump
+  - pmSourceRoughClass uses RDNOISE, GAIN to calculate SN: should use
+    the source->noise image? part of pmSourceMoments?
+  - pmSourceMoments_EAM uses macro for checkRegion
+  - pmObjects_EAM.c uses some hardcoded mask values (LocalSky)
+  - why does LocalSky_EAM not need subImageMask->col0 = subImage->col0??
+  - psGetRowVectorFromImage should be in psLib (in psLibUtils)
+  - pmSourceSetPixelsCircle and pmSourceLocalSky probably could use
+    the psImageMaskRegion, etc functions.
+  - pmSourceSetPixelsCircle uses a hard-coded mask value
+  - define a mask registration process??
+  - pmSourceContour is using the model flux, not the image flux
+
+2005.09.05
+
+  I have a working version of PSPhot which handles PSF and galaxy
+  models.  I am tagging the module with an alpha version number. this
+  version compiles with psLib-0.5.0, at least my version of it.  My
+  current goal is to make it work with the current psLib head (nearly
+  0.7.0) with a mimimum of _EAM versions of functions.
+
+  the tag for the working version is dev_01
+
+2005.06.04
+
+  progress on psphot has moved along well.  At this point, the process
+  loads the image, finds the peaks, determines a best PSF model,
+  identifies objects which are consistent with that model, and
+  attempts to fit a galaxy model to the objects which are not
+  consistent with the model. 
+
+  a variety of minor issues remain, as well as some major issues. 
+
+  - use a proper noise image to keep the fits honest after other
+    objects have been subtracted.  
+
+  - define the masks in terms of a single mask image.  this image
+    could be provided initially by the user.   
+
+  - add noise enhancement (couple with mask marking subtraction)
+
+  - 
+
+2005.04.12
+
+- psPeak, psSource, etc: should be pmPeak, pmSource, etc
+- pmCullPeaks: should be pmCullImagePeaks
+
+- does pmSourceMoments need an image argument?
+
+- psLibInit / p_psTimeInit
+
+  we should not have to know about astronomy time concepts (ie, have a
+  timeConfig file) in order to use the time functions for basic work.
+  This means having a PS_TIME_UNIX which just works with the UNIX
+  clock and avoids all initialization issues.
+
+- psImageStats is much too slow for basic median
+  - this function does a complete sort on all pixel values.  we need
+  to use the histogram method (spec a different type of median)
+
+- psImageStats, ROBUST_MEDIAN fails
+- there are errors in psVectorStats for ROBUST MEAN, STDEV (& median?)
+  * this is ill-defined.  revise the ADD: too sensitive to the
+  binning, smoothing scales 
+
+- pmFindImagePeaks is not finding any peaks:
+  typo in interior row section: see bug 359
+  after this fix: PASS
+
+* p_psGetRowVectorFromImage : PASS
+* psGetRowVectorFromImage : PASS 
+- both have about the same time (~15 us +/- 5us) for 2048 pix on alala
+
+* pmFindVectorPeaks : PASS (returned row data in data.U32)
+
+- # include <string.h> : needed in psMetadata.c
+
+* MyListAddPeak / pmPeakAlloc are inconsistent wrt col,row vs x,y
+
+- why does psMetadataItemAllocV allocate a string of length
+  MAX_STRING_LENGTH for the comment, then use strncpy?
+
+  should be:
+
+    // set metadata item comment
+    if (comment == NULL) {
+        // Per SDRS, null isn't allowed, must use "" instead
+        metadataItem->comment = psStringCopy ("");
+    } else {
+        metadataItem->comment = psStringCopy (comment);
+    }
+
+psMetadataAdd -> psMetadataAddItem is all confused:
+ - we end up with two psMetadataItemAlloc calls, when only one should
+   be used
+ - psMetadataItemAlloc was doing the wrong thing if the incoming data
+   was NULL
+ - I think the logic in this block is wrong as well.  
+
+ * I fixed this in psMetadata.c
+
+2005.04.14 : 
+
+ - psImageStats is still much too slow
+
+ - pmObjects : changed to using psArray to carry peaks / sources,
+   rather than psList
+
+ - added function pmPeaksSubset to replace pmCullPeaks
+
+ - pmSourceLocalSky: added min/max fncs to force subimage to stay in
+   image
+
+ - Sx, Sy can go negative: have forced limit to 0,0
+
+ - pmSourceRoughClass: implemented sigmaX, sigmaY search for stellar
+   clump (uses pmComparePeakDescend)
+
+   - the search ignores the 0,0 pixel
+
+   - pmSourceRoughClass uses all detected peaks.  allow an exclusion
+     for peak > max, peak < min?
+
+   - need to push clump stats on the metadata
+
+   - the source classes are assigned so they are mutually exclusive 
+
+   - change PS_SOURCE_OTHER to STAR? 
+
+ - pmSourceSetPixelCircle:
+
+   - name should be pmSourceSetPixelsCircle (geometry could be an
+     option...)
+
+   - I cleaned the defined region to match the convensions of
+     pmSourceLocalSky
+
+   - truncate and force subimage to lie on image
+
+   - CheckRadius2 seems like an inefficient coding
+
+   - valid pixels have mask value of 1 (inverse of other funcs)
+
+ - pmSourceFitModel:
+
+   - num iterations is much too large
+
+   - 
+
+mrq:
+
+ - allocate alpha, beta arrays
+ - mrq2dcof (pars, alpha, beta)
+ - make guess pars', alpha', beta'
+ - mrq2dcof (pars', alpha', beta')
+ - if (chisq < old chisq), keep new guess
+
+
+2005.04.19:
+
+- psMinimizeLMChi2 has some serious problems.  I re-wrote it starting
+  from the mrqmin example in ohana.   after a variety little bugs, it
+  seems to work quite well.  the program 'modeltest.c' runs two
+  different gaussians through psMinimizeLMChi2, and gets the right
+  answer quite quickly.  The first pass at the implementation had a
+  variety of problems.  to avoid any worry about errors in the
+  psMatrix code, I implemented psGaussJordan, basing the code on the
+  elixir gaussj.c code.  this worked fine, and let me find the errors
+  elsewhere in the code. the LUD version now works just the same as
+  the GaussJ version.
+
+
+2005.04.21
+
+- psMinimizeLMChi2 does an excellent job now in most cases.  I am able
+  to run pmSourceFitModel on the PSF stars quite well.  I added in the
+  (nearly) correct errors (actually, just using sqrt(N), limiting to
+  N>=1).  I see one problem object, which is quite faint, and does not
+  converge well: I get very large values for x,y, Sx, Sy, etc.  I need
+  to think about constraining these fits from running way off scale.
+
+  fit:   0.123879 sec for 277 stars (gauss)
+  fit:   0.155940 sec for 277 stars (pgauss) (!)
+  fit:   0.153292 sec for 277 stars
+  (0.5 msec per star)
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/outline.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/outline.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/outline.txt	(revision 22147)
@@ -0,0 +1,21 @@
+
+Here is a summary outline of the steps taken by psphotReadout:
+
+* setup (choose recipe, choose readout, define break-point)
+* create mask and weight images if needed, set mask for analysis region
+* psphotImageMedian : construct a background model image and subtract it
+* psphotFindPeaks   : smooth and find the peak pixels from the smoothed image
+  - no pmSource defined yet, only pmPeak
+* psphotSourceStats : create sources for each peak and measure their moments
+  - pmSource defined here, with pixels covering SKY_OUTER_RADIUS
+  - no pmModel is defined yet.
+* psphotBasicDeblend : identify blended sources by proximity and valley depth
+* psphotRoughClass : source classification guess based on moments
+  - set the source.type,mode.
+  - re-calculate moments for saturated stars
+* psphotChoosePSF : define the psf model
+  - this generates model fits to the identified psf stars
+    (one for each tested model)
+  - these models are not kept (seems like a waste, but later fits must be consistent)
+* psphotGuessModels : set the initial PSF model for each object (even EXTENDED)
+  - creates modelPSF entries
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/output.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/output.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/output.txt	(revision 22147)
@@ -0,0 +1,15 @@
+
+we have several output formats:
+  - TEXT : separate ASCII tables for PSF, EXT, MNT, and NULL
+  - SX : sextractor-style ASCII file
+  - OBJ : dophot-style ASCII file
+  - CMP : old-style elixir format (header + text)
+  - CMF : fits-table
+
+we have several options for organization:
+  - single chip/readout : outroot.ext
+  - split chips : outroot.NN.ext
+  - mef chips : outroot.ext (extensions with names)
+
+  * only CMF will work with MEF model
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/psfmodel.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/psfmodel.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/psfmodel.txt	(revision 22147)
@@ -0,0 +1,62 @@
+
+2007.09.25
+
+  The PSF model has parameters which vary across the image.  This 2D
+  is can be represented as either a polynomial (ordinary or chebychev)
+  or as an image map.  The user requests the maximum X & Y scale
+  (PSF.TREND.NX & PSF.TREND.NY) for the variations, and
+  psphotChoosePSF / pmPSFtry attempt to find the best choice for the
+  scale.  For the polynomial representations, NX and NY are the order
+  of the variation.  For the image map, NX and NY are the size of the
+  image map.
+
+2007.09.21
+
+  there are three places where we can choose to use errors in the fits or not:
+
+  * non-linear fitting of the models to the pixel flux distribution (poissonErrorsPhotLMM)
+  * linear fitting of the models to the pixel flux distribution (poissonErrorsPhotLin)
+  * fitting of the 2D variations in the psf parameters (poissonErrorsParams)
+  * fitting of the 2D variations in the aperture residuals
+
+2007.09.20
+
+  I am upgrading the PSF model to allow the parameter variation to be
+  modeled with pmTrend2D instead of just polynomials.  I am making a
+  list of places to modify the code:
+
+pmPSFAlloc : need a method beyong psfTrendMask to carry in the psf
+options
+
+pmPSF_ModelToFit : no need to change these
+
+update pmPSFBuildSimple to set the parameters of the pmTrend, which
+ever is used.
+
+pmPSFtry.c: some significant re-work!
+
+
+
+pmPSF_IO : need new functions to save / load the trend (psImages)
+
+2006.10.27
+
+  I have been working to fix the PSF modeling in psphot.  The PSF
+  model consists of a flux model for an individual object using an
+  analytical model with a number of parameters.  For a collection of
+  PSF objects, the variation of the parameters as a function of
+  position are then themselves fitted with a model.  The PSF model for
+  a single object consists of a radial profile with a functional form
+  f(z) where a given value of z defines an elliptical coutour of the
+  form z = \frac{x^2}{2\sigma_x^2} + \frac{y^2}{2\sigma_y^2} +
+  \sigma_{xy}xy.
+
+
+
+  The term \sigma_{xy} is difficult to model as a simple function of x
+  and y (eg, a low-order polynomial).  
+
+  A better model can be constructed for
+  \frac{\sigma_{xy}}{(\sigma_x^{-2} + \sigma_y^{-2})^2}, which varies
+  like a^2 \sin 2\theta 
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/psphot.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/psphot.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/psphot.txt	(revision 22147)
@@ -0,0 +1,160 @@
+
+TBD priorities:
+
+- test psImageSmooth / remove psImageSmooth_EAM
+- unify pmSourceLocalSky / pmSourceLocalSky_EAM
+- unify pmSourceMoments_EAM / pmSourceMoments_EAM
+- merge with image data hierarchy stuff
+- better name for psPSF_Test
+
+Defined APIs:
+
+top-level psphot functions : these do not need to be in psModules
+  psMetadata  *psphotArguments ();
+  psImageData *psphotSetup ();
+  psStats     *psphotImageStats ();
+  psArray     *pmPeaksSigmaLimit ();
+  psArray     *psphotSourceStats ();
+  pmPSF       *psphotChoosePSF ();
+  bool         psphotApplyPSF ();
+  bool         psphotFitGalaxies ();
+  void         psphotOutput ();
+
+psphot-specific functions
+  bool         psphotMarkPSF ();
+  bool         psphotSubtractPSF ();
+  int          psphotSortBySN ();
+  int          psphotSaveImage ();
+
+** add the following entries to the psLibSDRS
+
+psLib extra utilities
+  bool         psTimerStart ();		- already in SDRS
+  void         psTimerFree (); 		- already in SDRS
+  bool         psTimerClear ();		- add to psLibSDRS
+  psF64        psTimerMark (); 		- already in SDRS
+  psS32        psLogArguments ();	- now: psArgumentVerbosity in SDRS
+  psS32        psTraceArguments ();	- now: psArgumentVerbosity in SDRS
+  int          psArgumentGet ();	- already in SDRS
+  int          psArgumentRemove ();	- already in SDRS
+  psVector    *psVectorCreate ();	- add to psLibSDRS
+  int          psImageCountPixelMask ();- add to psLibSDRS
+  psVector    *psGetRowVectorFromImage(); - add to psLibSDRS?
+  bool         p_psVectorPrintRow ();     - add to psLibSDRS?
+
+// basic image functions
+  bool         psImageInit ();		- already in SDRS
+  void         psImageSmooth_EAM ();    - test psLib version and drop
+
+// psLine functions
+  psLine      *psLineAlloc ();		- add to psLibSDRS
+  bool         psLineInit (); 		- add to psLibSDRS
+  bool         psLineAdd ();  		- add to psLibSDRS
+
+** compare with current implementation / update psLib
+   we need to define a mechanism to carry the beta values and the
+   parameter limits
+
+// minimize 
+  psBool       p_psMinLM_GuessABP_EAM ();
+  psBool       psMinimizeLMChi2_EAM();
+  psF64        p_psMinLM_dLinear ();  -- not included in pslib.h files
+
+** already in psLib : some need to be cleaned up by MHPCC
+
+// polynomial functions
+  psF64           Polynomial2DEval();
+  psImage        *psBuildSums2D();
+  psPolynomial2D *VectorFitPolynomial2DOrd_EAM();
+  psPolynomial2D *Polynomial2DAlloc();
+  void            psPolynomial2DDump ();
+  psPolynomial2D *RobustFit2D_nomask();
+  psPolynomial2D *RobustFit2D();
+  psVector       *Polynomial2DEvalVector();
+
+  psVector       *psBuildSums1D();
+  void            psPolynomial1DDump ();
+  psF64           Polynomial1DEval_EAM();
+  psVector       *Polynomial1DEvalVector_EAM();
+  psPolynomial1D *Polynomial1DAlloc();
+  psPolynomial1D *VectorFitPolynomial1DOrd_EAM();
+
+** make sure these are all in the psModules SDRS:
+
+pmObjects.h:
+  pmMoments           *pmMomentsAlloc();
+  pmModel             *pmModelAlloc();
+  pmSource            *pmSourceAlloc();
+  psVector            *pmFindVectorPeaks()
+  psArray             *pmFindImagePeaks()
+  psList              *pmCullPeaks()
+  pmSource            *pmSourceLocalSky()
+  bool                 pmSourceMoments()
+  pmPSFClump           pmSourcePSFClump()
+  bool                 pmSourceRoughClass()
+  bool                 pmSourceSetPixelsCircle()
+  pmModel             *pmSourceModelGuess()
+  psArray             *pmSourceContour()
+  bool                 pmSourceFitModel()
+  bool                 pmSourceAddModel()
+  bool                 pmSourceSubModel()
+  int                  pmModelParameterCount ();
+  char                *pmModelGetType ();
+  pmModelType          pmModelSetType ();
+  pmModelFunc          pmModelFunc_GetFunction ();
+  pmModelFlux          pmModelFlux_GetFunction ();
+  pmModelRadius        pmModelRadius_GetFunction ();
+  pmModelLimits        pmModelLimits_GetFunction ();
+  pmModelGuessFunc     pmModelGuessFunc_GetFunction ();
+  pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction ();
+  pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction ();
+
+model function abstractions:
+  typedef psMinimizeLMChi2Func pmModelFunc;
+  typedef psF64 (*pmModelFlux)(const psVector *params);
+  typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+  typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+  typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+  typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+  typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+conversions between elliptical shape representations
+  EllipseAxes  EllipseMomentsToAxes ();
+  EllipseShape EllipseAxesToShape ();
+  EllipseAxes  EllipseShapeToAxes ();
+
+output functions
+  bool         pmSourcesWriteText ();  	-- be careful about psImageData...
+  bool         pmSourcesWriteOBJ ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMP ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMF ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteSX ();    	-- be careful about psImageData...
+  int          pmSourcesDophotType ();
+  bool         pmPeaksWriteText ();
+  bool         pmMomentsWriteText ();
+  bool         pmModelWritePSFs ();
+  bool         pmModelWriteFLTs ();
+  bool         pmModelWriteNULLs ();
+
+// psf utilities
+  pmPSF       *pmPSFAlloc ();
+  pmPSF_Test  *pmPSF_TestAlloc ();
+  pmPSF_Test  *pmPSF_TestModel ();
+  bool         pmPSFFromModels ();
+  pmModel     *pmModelFromPSF ();
+  bool         pmSourcePhotometry ();
+  bool         pmPSFMetricModel ();
+
+// psModule extra utilities
+  bool         pmSourceFitModel_EAM();  -- requires updated version of psMinimize...
+  bool         pmSourceLocalSky_EAM (); -- compare with pmSourceLocalSky & choose
+  bool         pmSourceMoments_EAM();   -- compare with pmSourceMoments & choose
+  bool         pmSourceDefinePixels();  -- be careful about psImageData...
+  bool         pmModelFitStatus ();
+  int          pmSourceDophotType ();
+  psF32        pmConfigLookupF32 ();	-- merge with Paul's image container hierarchy
+
+** this needs to be confronted with the phase2 / image container hierarchy
+
+// psImageData functions
+  psImageData *psImageDataAlloc ();
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/regions.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/regions.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/regions.txt	(revision 22147)
@@ -0,0 +1,159 @@
+
+I am having some trouble with functions that refer to both subimages
+and regions.  there are inconsistencies between the coordinate
+conventions.  I am going to go through the list of image functions are
+decide if we can choose either 1) external coordinates always refer to
+parent coordinates or 2) something else!
+
+bool psImageSet(const psImage *image, int x, int y, double complex value);
+double complex psImageGet(const psImage *image, int x, int y);
+ - sdrs does not specify
+ - code uses subimage
+
+psRegion psRegionForImage(psImage *image, psRegion in);
+ - sdrs says subimage
+ - code uses subimage
+
+psImage *psFitsReadImage(psImage *out, const psFits *fits, psRegion region, int z);
+ - region here specifies a subimage of the disk image
+ 
+bool psFitsUpdateImage(psFits *fits, const psImage *input, int x0, int y0, int z);
+ - x0,y0 here refer to the disk image coordinates
+
+bool psFitsWriteImage(psFits *fits, psMetadata *header, const psImage
+*input, int depth, const char *extname);
+ - note that the output disk image is truncated to the subimage
+
+bool psFitsInsertImage(psFits *fits, psMetadata *header, const psImage *input, int depth,
+                       const char *extname, bool after);
+ - same as above
+
+psImage *psImageSubset(psImage *image, psRegion region);
+ - code uses region as coordinates in parent image
+ - sdrs does not specify
+ - XXX I think the range tests for region fail if the input is a
+   subimage
+
+psImage *psImageCopy(psImage *output, const psImage *input, psElemType type);
+ - maintains the col0,row0 values
+
+psVector *psImageRow(psVector *out, const psImage *input, int row);
+psVector *psImageCol(psVector *out, const psImage *input, int column);
+ - code uses subimage coordinates for row and column
+
+psVector *psImageSlice()
+psVector* psImageCut()
+ - code uses subimage coordinates (no correction)
+ - sdrs does not specify
+
+psImage *psImageRebin()
+ - mask and input image must currently agree
+
+psImage *psImageTransform()
+ - sdrs is unclear: I think the region defines the output image such
+ that the pixel in the input image *parent* coordinates which
+ corresponds to region.x0,y0 in turn corresponds to the output image
+ 0,0 pixel; the output image always has col0,row0 = 0,0
+
+long psImageCountPixelMask (psImage *mask, psRegion region, psMaskType value);
+ - sdrs says the region corresponds to subimage coordinates
+
+psPolynomial2D *psImageFitPolynomial(psPolynomial2D *coeffs, const psImage *input);
+psImage *psImageEvalPolynomial(psImage *input, const psPolynomial2D *coeffs);
+ - sdrs does not specify
+ - polynomial coordinates should refer to the *parent* image
+
+double complex psImagePixelInterpolate(input, x, y, ...)
+ - sdrs does not specify
+
+int psImageOverlaySection(psImage *image, const psImage *overlay, )
+ - sdrs does not specify
+
+psImage *psPixelsToMask()
+psPixels *psPixelsFromMask()
+ - sdrs does not specify
+
+void psImageMaskRegion()
+void psImageKeepRegion()
+ - sdrs does not specify
+
+void psImageMaskCircle()
+void psImageKeepCircle()
+ - sdrs does not specify
+
+pmReadout *pmSubtractSky(pmReadout *in, psPolynomial2D *poly, psImage *mask, psU8 maskVal, 
+                         int binFactor, psStats *stats, float clipSD);
+ - choice of polynomial coordinates
+
+stats *pmFringeStats (psArray *fringePoints, psImage *image, psMetadata *config);
+ - unspecified
+
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row);
+ - sdrs says col,row are in *subimage* coords
+
+psArray *pmFindImagePeaks(const psImage *image, float threshold);
+ - does not specify coordinate system
+ - code uses subimage?
+
+bool pmSourceDefinePixels()
+bool pmSourceRedefinePixels()
+ - sdrs does not specify very clearly
+
+
+
+---------
+
+functions which are not affected
+psFitsReadImage()
+psFitsUpdateImage()
+psFitsWriteImage()
+psFitsInsertImage()
+psImageCopy()  -- maintains col0,row0
+psImageRebin() -- mask and image must match
+
+functions which currently use parent coordinates (CORRECT)
+psImageSubset()  -- check on range tests
+psImageMaskRegion()
+psImageKeepRegion()
+psImageMaskCircle()
+psImageKeepCircle()
+pmSourceMoments -- (OK if input peaks are in parent coords)
+
+functions which currently use subimage coordinates (FIX)
+psImageSet()
+psImageGet()
+psRegionForImage() *fixed*
+psImageRow()
+psImageCol()
+psImageSlice()
+psImageCut()
+psImageCountPixelMask ();
+pmModelEval()
+pmFindImagePeaks() *fixed*
+pmSourceDefinePixels() *fixed by psRegionForImage fix*
+pmSourceRedefinePixels() *fixed by psRegionForImage fix*
+
+functions which are unclear (CHECK)
+psImageTransform()
+psImageFitPolynomial()
+psImageEvalPolynomial()
+psImagePixelInterpolate()
+psPixelsToMask()
+pmSubtractSky()
+pmFringeStats()
+
+
+***** one ambiguity: if we have a subimage, but we do not have the
+      parent image data (subimage does not carry dimensions of parent
+      array), then a 0 or negative upper-edge of a region cannot be
+      defined relative to the parent. we can specify it relative to
+      the subimage.  so, for example, given a subimage [32,100:32,100]
+      of a much larger image, then psRegionForImage with 0,0,0,0 as
+      input would return a region with values 32,100:32,100...
+
+
+
+
+
+*** things I've modified
+    psRegionForImage expects parent (not subimage) coordinates
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/speed.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/speed.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/speed.txt	(revision 22147)
@@ -0,0 +1,129 @@
+
+psphot -g
+psModule -g
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.983568 sec
+    System random seed value used  seed = 25904F7C7454AE25 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063085 sec
+    System random seed value used  seed = 411E5A0600A49AF3 hex
+    fit background model: 0.580152 sec
+    smooth: 1.842415 sec
+    threshold: 83.100540 DN
+    4356 peaks: 0.571587 sec
+    4230 moments: 3.965099 sec
+    SN range: 3.355829 - 1043.401001
+    identified 107 blended objects (0.121737 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.682850 sec for 300 sources
+    fit psf:   1.704141 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717999
+    select psf model: 5.402742 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.991345 (4230 objects)
+    built matrix: 1.472869 (27998 elements)
+    measure ensemble of PSFs: 4.334556
+    fit PSF models: 12.205738 sec for 4230 objects
+    replace unfitted models: 0.021473 sec (4230 objects)
+    measure aperture residuals : 0.451886 sec
+    measure full-frame aperture residual: 0.454032 sec
+    aperture residual: -0.002842 +/- 0.002729 : 0.166072 bias
+    wrote output: 4.648821 sec
+    complete psphot run: 35.334080 sec
+
+
+psphot -g
+psModule -O2
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 1.001502 sec
+    System random seed value used  seed = 5F43A63F532D4B3F hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063169 sec
+    System random seed value used  seed = F5F5FF99A6578F61 hex
+    fit background model: 0.530384 sec
+    smooth: 1.856414 sec
+    threshold: 83.100540 DN
+    4365 peaks: 0.237292 sec
+    4239 moments: 4.087611 sec
+    SN range: 3.356469 - 1043.400879
+    identified 107 blended objects (0.123372 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.741444 sec for 300 sources
+    fit psf:   1.676478 sec for 300 sources
+    fit stats: 0.000883 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717985
+    select psf model: 5.458156 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.791613 (4239 objects)
+    built matrix: 1.267909 (28101 elements)
+    measure ensemble of PSFs: 3.551364
+    fit PSF models: 11.240197 sec for 4239 objects
+    replace unfitted models: 0.018051 sec (4239 objects)
+    measure aperture residuals : 0.411856 sec
+    measure full-frame aperture residual: 0.413843 sec
+    aperture residual: -0.002831 +/- 0.002717 : 0.164772 bias
+    wrote output: 4.355429 sec
+    complete psphot run: 33.126283 sec
+
+after reducing indirection in pmSourceMoments:
+    4268 moments: 3.762452 sec
+
+psphot -O2
+psModule -O2
+psLib -O2
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.484324 sec
+    System random seed value used  seed = 980050732A392EC7 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.058759 sec
+    System random seed value used  seed = 44E1A6EE77B0E9CE hex
+    fit background model: 0.395125 sec
+    smooth: 1.861776 sec
+    threshold: 83.100540 DN
+    4351 peaks: 0.234874 sec
+    4264 moments: 3.719435 sec
+    SN range: 2.023345 - 1043.401123
+    identified 124 blended objects (0.115230 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.525501 sec for 300 sources
+    fit psf:   1.595031 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717976
+    select psf model: 5.160442 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.760805 (4264 objects)
+    built matrix: 0.908674 (23703 elements)
+    measure ensemble of PSFs: 3.115425
+    fit PSF models: 10.162397 sec for 4264 objects
+    replace unfitted models: 0.017491 sec (4264 objects)
+    measure aperture residuals : 0.408242 sec
+    measure full-frame aperture residual: 0.410207 sec
+    aperture residual: -0.002908 +/- 0.002734 : 0.170692 bias
+    wrote output: 4.225560 sec
+    complete psphot run: 30.149281 sec
+
+
+    fit PSF models: 10.162397 sec for 4264 objects
+    select psf model: 5.160442 sec
+    wrote output: 4.225560 sec
+    4264 moments: 3.719435 sec
+    measure ensemble of PSFs: 3.115425 sec
+
+    other : 3.5 sec
+
+    fixed per objects : 4 msec / object (17 of 30 sec)
+
+
+** still can re-compile with -DPS_NO_TRACE
+** ASSERTS cannot be compiled out at the moment!
Index: /branches/eam_branches/eam_branch_20080229/psphot/doc/versions.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/doc/versions.txt	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/doc/versions.txt	(revision 22147)
@@ -0,0 +1,49 @@
+
+2006.02.02
+  I'm tagging psphot after I updated it to work with the readouts
+  using the structures being used for ppImage.  In this release, I
+  have also moved to autoconf for psphot, and the psphotReadout
+  portion is being defined as a library against which the other
+  elements are linked.  It should be possible to use this library with
+  ppImage, but I have not yet tested that.  
+
+  link psphot_dev_08 against psLib:eam_rel9_b2 and
+  psModule:eam_rel9_b2.
+
+2006.01.18
+  the tag below (psphot_dev_07) was moved to a later release.  It
+  should be used with psLib:eam_rel9_b1 from 2006/1/18 and
+  psModule:eam_rel9_p0.  
+
+2005.12.23
+  psphot_dev_07 should be compiled against psLib:eam_rel9_b1 and
+  psModule:eam_rel9_b1.  This version now handles the ensemble PSF
+  fitting, detection of blended sources, fitting of 'blends' (groups
+  of PSFs with distinct be 'blended' peaks) and 'blobs' (unresolved
+  extended objects, with a test for double PSF vs extended source
+  model.
+
+2005.11.25
+
+  psphot_dev_05 should be compiled against psLib tag eam_rel8_b2 and
+  psModules tag eam_rel8_b1.  this version removes all _EAM versions
+  of psLib and psModules code.  It now uses the psModules version of
+  the object code.
+
+  psphot_dev_04 should be compiled against psLib tag eam_rel8_b2 this
+  version removes all _EAM versions of psLib code, so that it is now
+  consistent with psLib.v8 (fixes are pushed into the eam_rel8_b2
+  branch).
+
+2005.11.16
+
+  psphot_dev_03 should be compiled against psLib tag eam_rel8_b1, a
+  branch starting from psLib rel8_0.  I needed to make some minor
+  fixes in psLib to work successfully with psLib rel8_0.
+
+  psphot_dev_02 was getting segfaults from a bug added into psLib in
+  rel8_0 in p_psVectorClippedStats.  I also fixed some errors arising
+  from changing types (argument to psMetadataParseConfig).
+
+
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/psphot-config.in
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/psphot-config.in	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/psphot-config.in	(revision 22147)
@@ -0,0 +1,87 @@
+#! /bin/sh
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+top_srcdir=@ABS_SRCDIR@
+
+usage()
+{
+    cat <<EOF
+Usage: psphot-config [OPTION]
+
+Known values for OPTION are:
+
+  --prefix		print psphot installation prefix
+  --libs		print library linking information
+  --cflags		print pre-processor and compiler flags
+  --build-libs		print library linking information to the build (non-installed) version
+  --build-cflags	print pre-processor and compiler flags to the build (non-installed) version
+  --help		display this help and exit
+  --version		output version information
+
+EOF
+
+    exit $1
+}
+
+if test $# -eq 0; then
+    usage 1
+fi
+
+cflags=false
+libs=false
+
+while test $# -gt 0; do
+    case "$1" in
+    -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+    *) optarg= ;;
+    esac
+
+    case "$1" in
+    --prefix=*)
+	prefix=$optarg
+	;;
+
+    --prefix)
+	echo $prefix
+	;;
+
+    --version)
+	echo @VERSION@
+	exit 0
+	;;
+
+    --help)
+	usage 0
+	;;
+
+    --cflags)
+       	echo -I${includedir} @PSPHOT_CFLAGS@
+       	;;
+
+    --libs)
+       	echo -L${libdir} -lpsphot @PSPHOT_LIBS@
+       	;;
+
+    --build-cflags)
+       	echo @SRCINC@ @PSPHOT_CFLAGS@
+       	;;
+
+    --build-libs)
+       	echo -L@ABS_SRCDIR@/src/.libs -lpsphot @PSPHOT_LIBS@
+       	;;
+
+    --deps)
+       	echo @PSPHOT_LIBS@
+       	;;
+    *)
+	usage
+	exit 1
+	;;
+    esac
+    shift
+done
+
+exit 0
Index: /branches/eam_branches/eam_branch_20080229/psphot/psphot.pc.in
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/psphot.pc.in	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/psphot.pc.in	(revision 22147)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libpsphot
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lpsphot
+Libs.private: @PSPHOT_LIBS@
+Cflags: -I${includedir}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/.cvsignore	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/.cvsignore	(revision 22147)
@@ -0,0 +1,18 @@
+*.o
+*.lo
+.libs
+.deps
+Makefile
+Makefile.in
+libpsphot.la
+psphot
+psphotTest
+psphot.loT
+psphotErrorCodes.h
+psphotErrorCodes.c
+config.h
+config.h.in
+stamp-h1
+
+polyfitTest
+growthTest
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/Makefile.am	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/Makefile.am	(revision 22147)
@@ -0,0 +1,103 @@
+
+lib_LTLIBRARIES = libpsphot.la
+libpsphot_la_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+
+bin_PROGRAMS = psphot
+
+psphot_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+psphot_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSPHOT_LIBS)
+psphot_LDADD = libpsphot.la
+
+psphot_SOURCES = \
+        psphot.c                \
+	psphotArguments.c	\
+	psphotCleanup.c	   	\
+	psphotImageLoop.c	\
+	psphotMosaicChip.c	\
+	psphotParseCamera.c
+
+libpsphot_la_SOURCES = \
+	psphotErrorCodes.c	 \
+	pmFootprint.c		 \
+	psphotVersion.c		 \
+	psphotModelGroupInit.c	 \
+	psphotMaskReadout.c	 \
+	psphotDefineFiles.c	 \
+	psphotReadout.c		 \
+	psphotModelBackground.c	 \
+	psphotSubtractBackground.c \
+	psphotFindDetections.c	 \
+	psphotFindPeaks.c	 \
+	psphotSourceStats.c	 \
+	psphotRoughClass.c	 \
+	psphotBasicDeblend.c	 \
+	psphotChoosePSF.c	 \
+	psphotGuessModels.c      \
+	psphotFitSourcesLinear.c \
+	psphotBlendFit.c	 \
+	psphotReplaceUnfit.c	 \
+	psphotApResid.c		 \
+	psphotMagnitudes.c	 \
+	psphotSkyReplace.c	 \
+	psphotEvalPSF.c		 \
+	psphotEvalFLT.c		 \
+	psphotSourceFits.c	 \
+	psphotRadiusChecks.c	 \
+	psphotOutput.c		 \
+	psphotGrowthCurve.c	 \
+	psphotFakeSources.c	 \
+	psphotModelWithPSF.c     \
+	psphotExtendedSources.c	 \
+	psphotRadialProfile.c	 \
+	psphotPetrosian.c	 \
+	psphotIsophotal.c	 \
+	psphotAnnuli.c		 \
+	psphotKron.c		 \
+	psphotKernelFromPSF.c	 \
+	psphotPSFConvModel.c	 \
+	psphotModelTest.c	 \
+	psphotFitSet.c		 \
+	psphotSourceFreePixels.c \
+	psphotSummaryPlots.c     \
+	psphotMergeSources.c	 \
+	psphotLoadPSF.c	         \
+	psphotReadoutCleanup.c	 \
+	psphotSourcePlots.c	 \
+	psphotRadialPlot.c	 \
+	psphotDeblendSatstars.c	 \
+	psphotMosaicSubimage.c	 \
+	psphotMakeResiduals.c	 \
+	psphotSourceSize.c	 \
+	psphotDiagnosticPlots.c	 \
+	psphotMakeFluxScale.c	 \
+	psphotCheckStarDistribution.c \
+	psphotAddNoise.c
+
+include_HEADERS = \
+	psphot.h \
+	pmFootprint.h \
+	psphotErrorCodes.h
+
+noinst_HEADERS = \
+	psphotInternal.h \
+	psphotStandAlone.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+# Error codes.
+BUILT_SOURCES = psphotErrorCodes.h psphotErrorCodes.c
+CLEANFILES = psphotErrorCodes.h psphotErrorCodes.c
+EXTRA_DIST = psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h.in \
+	models/pmModel_STRAIL.c \
+	models/pmModel_TEST1.c
+
+psphotErrorCodes.h : psphotErrorCodes.dat psphotErrorCodes.h.in
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.h
+
+psphotErrorCodes.c : psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.c
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/models/pmModel_STRAIL.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/models/pmModel_STRAIL.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/models/pmModel_STRAIL.c	(revision 22147)
@@ -0,0 +1,618 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = length;
+    params->data.F32[8] = theta;
+*****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_STRAIL
+# define PM_MODEL_FLUX            pmModelFlux_STRAIL
+# define PM_MODEL_GUESS           pmModelGuess_STRAIL
+# define PM_MODEL_LIMITS          pmModelLimits_STRAIL
+# define PM_MODEL_RADIUS          pmModelRadius_STRAIL
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_STRAIL
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_STRAIL
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_STRAIL
+
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 trailLength = PAR[7];
+    psF32 theta = PAR[8];
+
+    psF32 x0 = PAR[2];  //streak center
+    psF32 y0 = PAR[3];  //streak center
+
+    //S values (1/sigma for x and y case, sigma for xy case)
+    psF32 sx = PAR[4];
+    psF32 sy = PAR[5];
+    psF32 sxy = PAR[6];
+
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+    psF32 sin2T=sin(2.0*theta);
+    psF32 cos2T=cos(2.0*theta);
+
+    //    printf("Trying object at %4.1f,%4.1f with length %3.1f and angle %1.3f\r", x0, y0, length, theta);
+
+    //current location relative to trail center
+    psF32 X  = x->data.F32[0] - x0;
+    psF32 Y  = x->data.F32[1] - y0;
+
+    //x' and y' location (trail-orienter coords)
+    psF32 xs = X*cosT + Y*sinT;
+    psF32 ys = -1.0*X*sinT + Y*cosT;
+
+    //initialize variables to be changed below
+    psF32 x1 = 0;
+    psF32 y1 = 0;
+    psF32 px = 0;
+    psF32 py = 0;
+    psF32 z  = 0;
+    psF32 zx = 0;
+    psF32 t  = 0;
+    psF32 tx = 0;
+    psF32 r  = 0;
+    psF32 rx = 0;
+    psF32 f  = 0;
+
+    psF32 sxrot = 0;
+    psF32 syrot = 0;
+    psF32 sxyrot = 0;
+    psF32 dsxrot = 0;
+    psF32 dsyrot = 0;
+    psF32 dsxyrot = 0;
+
+    //    psF32 Rx = 0;
+    //    psF32 Ry = 0;
+    //    psF32 Rxy = 0;
+
+
+    //calculate new S values (1/sigma) for rotated frame
+    psF32 sxrotsq = PS_SQR(cosT*sx) + PS_SQR(sinT*sy) + cosT*sinT*sxy;
+    psF32 syrotsq = PS_SQR(cosT*sy) + PS_SQR(sinT*sx) - cosT*sinT*sxy;
+
+    //    psF32 testtwo=10.1;
+    //    psF32 testone=fabsf(testtwo);
+    //    fprintf (stderr, "Test: %f is the absolute value of %f?\n",testone,testtwo);
+    if (sxrotsq<0) {
+      sxrot = sqrt(-(sxrotsq));
+      syrot = sqrt(syrotsq);
+      fprintf (stderr, "error in sxrotsq: Neg,  sxrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",sxrotsq,sx,sy,sxy,theta);
+    } else if (syrotsq<0) {
+      sxrot = sqrt(sxrotsq);
+      syrot = sqrt(-(syrotsq));
+      fprintf (stderr, "error in syrotsq: Neg,  syrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",syrotsq,sx,sy,sxy,theta);
+    } else if (sxrotsq==0){
+      sxrot = 0.01;
+      syrot = sqrt(syrotsq);
+      fprintf (stderr, "error in sxrotsq: Zero,  sxrotsq=%f \n",sxrotsq);
+    } else if (syrotsq==0) {
+      syrot = 0.01;
+      sxrot = sqrt(sxrotsq);
+      fprintf (stderr, "error in syrotsq: Zero,  syrotsq=%f \n",syrotsq);
+      //      return(0);
+    }else {
+      sxrot = sqrt(sxrotsq);
+      syrot = sqrt(syrotsq);
+    }
+
+    sxyrot = sxy*cos2T + (PS_SQR(sy) - PS_SQR(sx))*sin2T;
+
+      if (isnan(sxrot)) {
+        fprintf (stderr, "error in sxrot  syrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",syrot,sx,sy,sxy,cosT,sinT);
+      } else if (isnan(syrot)) {
+        fprintf (stderr, "error in syrot  sxrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",sxrot,sx,sy,sxy,cosT,sinT);
+      }
+
+    //calculate length of pipe (not of trail motion)
+    psF32 length = trailLength - 2.0*2.0/sxrot;
+
+
+    if (xs < length/(-2.0)) {
+      x1 = (xs+length/2.0)*cosT - ys*sinT; //Endpoint1
+      y1 = (xs+length/2.0)*sinT + ys*cosT; //Endpoint1
+      //1.6 factor comes from by-eye testing of fits, sqrt from the later squaring of it
+      //1.6 ~= phi (golden mean)...coincidence?
+      px = sxrot*x1/sqrt(1.6);
+      py = syrot*y1;
+
+      //first find out what the falloff in the x direction is...
+      zx  = 0.5*PS_SQR(px);
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0;
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0);
+
+      //...and now in the y direction
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      f  = PAR[1]*rx*r + PAR[0];
+
+    } else if (xs > length/2.0){
+      x1 = (xs-length/2.0)*cosT - ys*sinT; //Endpoint2
+      y1 = (xs-length/2.0)*sinT + ys*cosT; //Endpoint2
+      px = sxrot*x1/sqrt(1.6);
+      py = syrot*y1;
+      zx  = 0.5*PS_SQR(px);
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0;
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0);
+
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      f  = PAR[1]*rx*r + PAR[0];
+
+      if (isnan(r)) {
+        fprintf (stderr, "error in +r  t=%f z=%f\n",t,z);
+      }
+
+    } else {
+      x1 = -ys*sinT;
+      y1 = ys*cosT;
+      px = sx*x1;
+      py = sy*y1;
+      z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + sxy*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      rx = 1.0;  //so that dF/dF0 can be generalized
+      f  = PAR[1]*r + PAR[0];
+
+      if (isnan(r)) {
+        fprintf (stderr, "error in midr  t=%f z=%f\n",t,z);
+      }
+    }
+
+
+    //ok...so this is df/dPAR[X]
+    if (deriv != NULL) {
+        psF32 q = 1;
+        //stable
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r * rx;
+
+          if (isnan(deriv->data.F32[0])) {
+            fprintf (stderr, "error in deriv0\n");
+          } else if (isnan(deriv->data.F32[1])) {
+            fprintf (stderr, "error in deriv1 r=%f rx=%f\n",r,rx);
+          }
+
+        dsxrot = 2.0*PS_SQR(sy)*sinT*cosT - 2.0*PS_SQR(sx)*sinT*cosT + sxy*(PS_SQR(cosT)-PS_SQR(sinT));
+        dsyrot = 2.0*PS_SQR(sx)*sinT*cosT - 2.0*PS_SQR(sy)*sinT*cosT - sxy*(PS_SQR(cosT)-PS_SQR(sinT));
+        dsxyrot = 2.0*cos2T*(PS_SQR(sy)-PS_SQR(sx)) - 2.0*sxy*sin2T;
+
+          if (isnan(dsxrot)) {
+            fprintf (stderr, "error in dsxrot\n");
+          } else if (isnan(dsyrot)) {
+            fprintf (stderr, "error in dsyrot\n");
+          } else if (isnan(dsxyrot)) {
+            fprintf (stderr, "error in dsxyrot\n");
+          }
+
+        //initialize variables definied in the if statements
+        //      psF32 XXX=0;
+        //      psF32 YYY=0;
+
+
+        // variable over piecewise func
+        // change the endcaps to be 4th order gaussians with sxrot_fit=1.6*sxrot
+        // y' direction can ge adequately modelled by a 3rd order gaussian with syrot
+        if (xs < length/(-2.0)) {
+
+          q=PAR[1]*r*rx;
+          deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot);
+          deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot);
+          deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT+2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T+y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)+2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T+y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1+2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1+2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T+y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)+x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[7] = -q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT));
+          deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)+y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)+x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT)));
+
+          /*      if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+          */
+
+
+
+        } else if (xs > length/2.0){
+
+          q=PAR[1]*r*rx;
+          deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot);
+          deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot);
+          deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT-2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T-y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)-2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T-y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1-2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1-2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T-y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)-x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[7] = q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT));
+          deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)-y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)-x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT)));
+
+
+          /*      if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+          */
+
+        } else {
+          // this does not change from before, as the y' falloff can be modelled by the standard 3rd order gaussian
+          // note difference from a pure gaussian: q = PAR[1]*r
+          q = PAR[1]*r*r*t;
+          deriv->data.F32[2] = q*(PS_SQR(sx*sinT)*x1 - PS_SQR(sy)*y1*cosT*sinT - sxy*x1*sinT*cosT + sxy*y1*PS_SQR(sinT));
+          deriv->data.F32[3] = q*(-1*PS_SQR(sx)*x1*sinT*cosT + PS_SQR(sy*cosT)*y1 + sxy*x1*PS_SQR(cosT) - sxy*y1*sinT*cosT);
+          deriv->data.F32[4] = -q*sx*PS_SQR(x1);
+          deriv->data.F32[5] = -q*sy*PS_SQR(y1);
+          deriv->data.F32[6] = -q*x1*y1;
+          deriv->data.F32[7] = 0;
+          deriv->data.F32[8] = -q*( PS_SQR(sx)*x1*(xs*sinT - ys*cosT) + PS_SQR(sy)*y1*(xs*cosT - ys*sinT) + sxy*x1*(xs*cosT - ys*sinT) + sxy*y1*(xs*sinT - ys*cosT) );
+
+          if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+
+
+        }
+    }
+    return(f);
+}
+
+//fixed
+// XXX this needs to apply the axis ratio limits to prevent avoid solutions
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta) {
+
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+    float f1, f2, q1;
+    float q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        assert (q1 > 0);
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+          case PM_PAR_SKY:  beta_lim = 1000;   break;
+          case PM_PAR_I0:   beta_lim = 10000;    break; // too small?
+          case PM_PAR_XPOS: beta_lim = 50;     break;
+          case PM_PAR_YPOS: beta_lim = 50;     break;
+          case PM_PAR_SXX:  beta_lim = 0.5;    break;
+          case PM_PAR_SYY:  beta_lim = 0.5;    break;
+          case PM_PAR_SXY:  beta_lim = 1.0;    break;  // set this to q2?
+          case 7:           beta_lim = 10.0;     break;
+          case 8:           beta_lim = M_PI/6.0; break;
+
+          default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_min = -1000; break;
+          case PM_PAR_I0:   params_min =     0; break;
+          case PM_PAR_XPOS: params_min =  -100; break;
+          case PM_PAR_YPOS: params_min =  -100; break;
+          case PM_PAR_SXX:  params_min =   0.5; break;
+          case PM_PAR_SYY:  params_min =   0.5; break;
+          case PM_PAR_SXY:  params_min =  -5.0; break; // set this to -q2?
+          case 7:           params_min =     0;  break;
+          case 8:           params_min = -1*M_PI; break;
+
+          default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_max =   1e5; break;
+          case PM_PAR_I0:   params_max =   1e8; break;
+          case PM_PAR_XPOS: params_max =   1e4; break;
+          case PM_PAR_YPOS: params_max =   1e4; break;
+          case PM_PAR_SXX:  params_max =   100; break;
+          case PM_PAR_SYY:  params_max =   100; break;
+          case PM_PAR_SXY:  params_max =   +q2; break;
+          case 7:           params_max =   150; break;
+          case 8:           params_max =  M_PI; break;
+          default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            return false;
+        }
+        return true;
+      default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+//fixed
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF32 Rx=2./PS_SQR(PAR[4]);
+    psF32 Ry=2./PS_SQR(PAR[5]);
+    psF32 Rxy=PAR[6];
+
+
+    psF32 theta = PAR[8];
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y
+
+    psF64 A4   = Syrot*PAR[7];
+
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3) + A4;
+    // Area is equivalent to 2 pi sigma^2 + rectangle
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        f = 1.0 / (1 + z + z*z/2 + z*z*z/6);
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = params->data.F32[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// also prevent 0 returns, and just send a v small number
+// return the radius which yields the requested flux
+
+//fixed, but need to change how it is called to accomodate 2 radii
+psF64 PM_MODEL_RADIUS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1]) return (1.0);
+
+    psF32 *PAR = params->data.F32;
+    psF32 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+
+    psF32 theta = PAR[8];
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+    psF32 Rx=2./PS_SQR(PAR[4]);
+    psF32 Ry=2./PS_SQR(PAR[5]);
+    psF32 Rxy=PAR[6];
+    psF32 length=PAR[7];
+
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y
+
+    psF64 radius = 0;
+    if (flux > 0){
+      psF64 radius0 = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+      psF64 radius1 = Syrot * sqrt (2.0 * log(params->data.F32[1] / flux));
+
+      if (radius0 > radius1) {
+        radius=radius0+length/2.0;
+      } else {
+        radius=radius1+length/2.0;
+      }
+    } else {
+      radius = 1000;
+    }
+
+    if (radius < 0.01){
+      radius = 0.01;
+    }
+
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+//fixed I think...no good way of guessing as far as I can tell
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *Smoments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(Smoments->Sx);
+    moments.y2 = PS_SQR(Smoments->Sy);
+    moments.xy = Smoments->Sxy;
+    //sometimes these moment inputs are zero...why?
+
+    // solve the math to go from Moments To Shape
+    // limit the axis ratio to 20 (a guess)
+    axes = psEllipseMomentsToAxes(moments, 20.0);
+    shape = psEllipseAxesToShape(axes);
+
+    params[0] = Smoments->Sky;
+    params[1] = Smoments->Peak - Smoments->Sky;
+    params[2] = Smoments->x;
+    params[3] = Smoments->y;
+    params[7] = 2 * axes.major;
+    params[8] = axes.theta;
+
+    if (moments.x2 == 0 || moments.y2 == 0){
+      params[4] = 2.0;
+      params[5] = 2.0;
+      params[6] = 0.0;
+    } else {
+      params[4] = 1.0 / shape.sx;
+      params[5] = 1.0 / shape.sy;
+      params[6] = shape.sxy;
+    }
+
+    //    printf("Who is NaN? momx: %4.3f  momy: %4.3f  momxy: %4.3f\n", moments.x2,moments.y2, moments.xy);
+
+    return(true);
+}
+
+//fixed
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+    out[7] = in[7];
+    out[8] = in[8];
+
+    for (int i = 4; i < 7; i++) {
+      pmTrend2D *trend = psf->params->data[i-4];
+        out[i] = pmTrend2DEval (trend, out[2], out[3]);
+    }
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+//done I think
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+    //    status &= ((dPAR[7]/PAR[7]) < 0.5);
+    //    status &= ((dPAR[8]/PAR[8]) < 0.5);
+
+    if (status) return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/models/pmModel_TEST1.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/models/pmModel_TEST1.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/models/pmModel_TEST1.c	(revision 22147)
@@ -0,0 +1,311 @@
+/******************************************************************************
+ * this file defines the TEST1 source shape model.  Note that these model functions are loaded
+ * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
+
+ * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+ * PM_PAR_I0 1    - central intensity
+ * PM_PAR_XPOS 2  - X center of object
+ * PM_PAR_YPOS 3  - Y center of object
+ * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) * SigmaX)
+ * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) * SigmaY)
+ * PM_PAR_SXY 6   - X*Y term of elliptical contour
+ *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_TEST1
+# define PM_MODEL_FLUX            pmModelFlux_TEST1
+# define PM_MODEL_GUESS           pmModelGuess_TEST1
+# define PM_MODEL_LIMITS          pmModelLimits_TEST1
+# define PM_MODEL_RADIUS          pmModelRadius_TEST1
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_TEST1
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_TEST1
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_TEST1
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                    const psVector *params,
+                    const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    // XXX this is fitting sigma_x/sqrt(2), sigma_y/sqrt(2)
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    psF32 t  = 1 + z + z*z/2.0;
+    psF32 r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+        psF32 q = PAR[PM_PAR_I0]*r*r*t;
+        dPAR[PM_PAR_SKY]  = +1.0;
+        dPAR[PM_PAR_I0]   = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_SXX]  = +2.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY]  = +2.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY]  = -q*X*Y;
+    }
+    return(f);
+}
+
+// define the parameter limits
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+          case PM_PAR_SKY:  beta_lim = 1000;  break;
+          case PM_PAR_I0:   beta_lim = 3e6;   break;
+          case PM_PAR_XPOS: beta_lim = 5;     break;
+          case PM_PAR_YPOS: beta_lim = 5;     break;
+          case PM_PAR_SXX:  beta_lim = 0.5;   break;
+          case PM_PAR_SYY:  beta_lim = 0.5;   break;
+          case PM_PAR_SXY:  beta_lim = 0.5;   break;
+          default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_min = -1000; break;
+          case PM_PAR_I0:   params_min =     0; break;
+          case PM_PAR_XPOS: params_min =  -100; break;
+          case PM_PAR_YPOS: params_min =  -100; break;
+          case PM_PAR_SXX:  params_min =   0.5; break;
+          case PM_PAR_SYY:  params_min =   0.5; break;
+          case PM_PAR_SXY:  params_min =  -5.0; break;
+          default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_max =   1e5; break;
+          case PM_PAR_I0:   params_max =   1e8; break;
+          case PM_PAR_XPOS: params_max =   1e4; break;
+          case PM_PAR_YPOS: params_max =   1e4; break;
+          case PM_PAR_SXX:  params_max =   100; break;
+          case PM_PAR_SYY:  params_max =   100; break;
+          case PM_PAR_SXY:  params_max =  +5.0; break;
+          default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            return false;
+        }
+        return true;
+      default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    psF32     *PAR  = model->params->data.F32;
+
+    PAR[PM_PAR_SKY] = moments->Sky;
+    PAR[PM_PAR_I0] = moments->Peak - moments->Sky;
+    PAR[PM_PAR_XPOS] = moments->x;
+    PAR[PM_PAR_YPOS] = moments->y;
+    PAR[PM_PAR_SXX] = PS_MAX(0.5, moments->Sx);
+    PAR[PM_PAR_SYY] = PS_MAX(0.5, moments->Sy);
+    PAR[PM_PAR_SXY] = 0.0;  // XXX we can get this right if we do the integral
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float norm, z;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+# define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        z += DZ;
+        f2 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[PM_PAR_I0])
+        return (1.0);
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // this estimates the radius assuming f(z) is roughly exp(-z)
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 radius = axes.major * sqrt (2.0 * log(params->data.F32[PM_PAR_I0] / flux));
+
+    if (isnan(radius)) psAbort("error in code: never return invalid radius");
+    if (radius < 0) psAbort("error in code: never return invalid radius");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the 2D model for SXY actually fits SXY / (SXX^-2 + SYY^-2); correct here
+    out[PM_PAR_SXY] = pmPSF_SXYtoModel (out);
+
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+// XXX double-check these definitions below
+// this test is invalid if the parameters are derived
+// from the PSF model
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/pmFootprint.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/pmFootprint.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/pmFootprint.c	(revision 22147)
@@ -0,0 +1,1291 @@
+/*****************************************************************************/
+/*
+ * Handle pmFootprints
+ */
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+#include "pmFootprint.h"
+
+static void spanFree(pmSpan *tmp) {
+   ;
+}
+
+/*
+ * pmSpanAlloc()
+ */
+pmSpan *pmSpanAlloc(int y, int x0, int x1)
+{
+    pmSpan *span = (pmSpan *)psAlloc(sizeof(pmSpan));
+    psMemSetDeallocator(span, (psFreeFunc) spanFree);
+
+    span->y = y;
+    span->x0 = x0;
+    span->x1 = x1;
+
+    return(span);
+}
+
+bool pmSpanTest(const psPtr ptr)
+{
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)spanFree);
+}
+
+//
+// Sort pmSpans by y, then x0, then x1
+//
+int pmSpanSortByYX(const void **a, const void **b) {
+    const pmSpan *sa = *(const pmSpan **)a;
+    const pmSpan *sb = *(const pmSpan **)b;
+
+    if (sa->y < sb->y) {
+	return -1;
+    } else if (sa->y == sb->y) {
+	if (sa->x0 < sb->x0) {
+	    return -1;
+	} else if (sa->x0 == sb->x0) {
+	    if (sa->x1 < sb->x1) {
+		return -1;
+	    } else if (sa->x1 == sb->x1) {
+		return 0;
+	    } else {
+		return 1;
+	    }
+	} else {
+	    return 1;
+	}
+    } else {
+	return 1;
+    }
+}
+
+/************************************************************************************************************/
+
+static void footprintFree(pmFootprint *tmp)
+{
+   if (!tmp) {
+        return;
+   }
+
+   psTrace("psModules.objects", 5, "---- begin ----\n");
+
+   psFree(tmp->spans);
+   psFree(tmp->peaks);
+
+   psTrace("psModules.objects", 5, "---- end ----\n");
+}
+
+/*
+ * pmFootprintAlloc(): Allocate the pmFootprint structure to NULL.
+ */
+pmFootprint *pmFootprintAlloc(int nspan, // number of spans expected in footprint
+			      const psImage *image) // region footprint lives in
+{
+    psTrace("psModules.objects", 5, "---- begin ----\n");
+
+    static int id = 1;
+    pmFootprint *footprint = (pmFootprint *)psAlloc(sizeof(pmFootprint));
+    *(int *)&footprint->id = id++;
+    psMemSetDeallocator(footprint, (psFreeFunc) footprintFree);
+
+    footprint->normalized = false;
+
+    assert(nspan >= 0);
+    footprint->npix = 0;
+    footprint->spans = psArrayAllocEmpty(nspan);
+    footprint->peaks = psArrayAlloc(0);
+
+    footprint->bbox.x0 = footprint->bbox.y0 = 0;
+    footprint->bbox.x1 = footprint->bbox.y1 = -1;
+
+    if (image == NULL) {
+	footprint->region.x0 = footprint->region.y0 = 0;
+	footprint->region.x1 = footprint->region.y1 = -1;
+    } else {
+	footprint->region.x0 = image->col0;
+	footprint->region.x1 = image->col0 + image->numCols - 1;
+	footprint->region.y0 = image->row0;
+	footprint->region.y1 = image->row0 + image->numRows - 1;
+    }
+
+    psTrace("psModules.objects", 5, "---- end ----\n");
+    return(footprint);
+}
+
+bool pmFootprintTest(const psPtr ptr) {
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)footprintFree);
+}
+
+pmFootprint *pmFootprintNormalize(pmFootprint *fp) {
+    if (fp != NULL && !fp->normalized) {
+	fp->peaks = psArraySort(fp->peaks, pmPeakSortBySN);
+	fp->normalized = true;
+    }
+
+    return fp;
+}
+
+//
+// Add a span to a footprint, returning the new span
+//
+static pmSpan *pmFootprintAddSpan(pmFootprint *fp,	// the footprint to add to
+				  const int y, // row to add
+				  int x0,      // range of
+				  int x1) {    //          columns
+
+    if (x1 < x0) {
+	int tmp = x0;
+	x0 = x1;
+	x1 = tmp;
+    }
+
+    pmSpan *sp = pmSpanAlloc(y, x0, x1);
+    psArrayAdd(fp->spans, 1, sp);
+    psFree(sp);
+    
+    fp->npix += x1 - x0 + 1;
+
+    if (fp->spans->n == 1) {
+	fp->bbox.x0 = x0;
+	fp->bbox.x1 = x1;
+	fp->bbox.y0 = y;
+	fp->bbox.y1 = y;
+    } else {
+	if (x0 < fp->bbox.x0) fp->bbox.x0 = x0;
+	if (x1 > fp->bbox.x1) fp->bbox.x1 = x1;
+	if (y < fp->bbox.y0) fp->bbox.y0 = y;
+	if (y > fp->bbox.y1) fp->bbox.y1 = y;
+    }
+
+    return sp;
+}
+
+void pmFootprintSetBBox(pmFootprint *fp) {
+    assert (fp != NULL);
+    if (fp->spans->n == 0) {
+	return;
+    }
+    pmSpan *sp = fp->spans->data[0];
+    int x0 = sp->x0;
+    int x1 = sp->x1;
+    int y0 = sp->y;
+    int y1 = sp->y;
+
+    for (int i = 1; i < fp->spans->n; i++) {
+	sp = fp->spans->data[i];
+	
+	if (sp->x0 < x0) x0 = sp->x0;
+	if (sp->x1 > x1) x1 = sp->x1;
+	if (sp->y < y0) y0 = sp->y;
+	if (sp->y > y1) y1 = sp->y;
+    }
+
+    fp->bbox.x0 = x0;
+    fp->bbox.x1 = x1;
+    fp->bbox.y0 = y0;
+    fp->bbox.y1 = y1;
+}
+
+int pmFootprintSetNpix(pmFootprint *fp) {
+   assert (fp != NULL);
+   int npix = 0;
+   for (int i = 0; i < fp->spans->n; i++) {
+       pmSpan *span = fp->spans->data[i];
+       npix += span->x1 - span->x0 + 1;
+   }
+   fp->npix = npix;
+
+   return npix;
+}
+
+/************************************************************************************************************/
+
+typedef struct {			/* run-length code for part of object*/
+   int id;				/* ID for object */
+   int y;				/* Row wherein WSPAN dwells */
+   int x0, x1;				/* inclusive range of columns */
+} WSPAN;
+
+/*
+ * comparison function for qsort; sort by ID then row
+ */
+static int
+compar(const void *va, const void *vb)
+{
+   const WSPAN *a = va;
+   const WSPAN *b = vb;
+
+   if(a->id < b->id) {
+      return(-1);
+   } else if(a->id > b->id) {
+      return(1);
+   } else {
+      return(a->y - b->y);
+   }
+}
+
+/*
+ * Follow a chain of aliases, returning the final resolved value.
+ */
+static int
+resolve_alias(const int *aliases,	/* list of aliases */
+	      int id)			/* alias to look up */
+{
+   int resolved = id;			/* resolved alias */
+
+   while(id != aliases[id]) {
+      resolved = id = aliases[id];
+   }
+
+   return(resolved);
+}
+
+/*
+ * Go through an image, finding sets of connected pixels above threshold
+ * and assembling them into pmFootprints;  the resulting set of objects
+ * is returned as a psArray
+ */
+psArray *
+pmFindFootprints(const psImage *img,	// image to search
+		 const float threshold,	// Threshold
+		 const int npixMin)	// minimum number of pixels in an acceptable pmFootprint
+{
+   int i0;				/* initial value of i */
+   int id;				/* object ID */
+   int in_span;				/* object ID of current WSPAN */
+   int nspan = 0;			/* number of spans */
+   int nobj = 0;			/* number of objects found */
+   int x0 = 0;			        /* unpacked from a WSPAN */
+   int *tmp;				/* used in swapping idc/idp */
+
+   assert(img != NULL);
+
+   bool F32 = false;			// is this an F32 image?
+   if (img->type.type == PS_TYPE_F32) {
+       F32 = true;
+   } else if (img->type.type == PS_TYPE_S32) {
+       F32 = false;
+   } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
+       psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
+       return NULL;
+   }
+   psF32 *imgRowF32 = NULL;		// row pointer if F32
+   psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
+   
+   const int row0 = img->row0;
+   const int col0 = img->col0;
+   const int numRows = img->numRows;
+   const int numCols = img->numCols;
+/*
+ * Storage for arrays that identify objects by ID. We want to be able to
+ * refer to idp[-1] and idp[numCols], hence the (numCols + 2)
+ */
+   int *id_s = psAlloc(2*(numCols + 2)*sizeof(int));
+   memset(id_s, '\0', 2*(numCols + 2)*sizeof(int)); assert(id_s[0] == 0);
+   int *idc = id_s + 1;			// object IDs in current/
+   int *idp = idc + (numCols + 2);	//                       previous row
+
+   int size_aliases = 1 + numRows/20;	// size of aliases[] array
+   int *aliases = psAlloc(size_aliases*sizeof(int)); // aliases for object IDs
+
+   int size_spans = 1 + numRows/20;	// size of spans[] array
+   WSPAN *spans = psAlloc(size_spans*sizeof(WSPAN)); // row:x0,x1 for objects
+/*
+ * Go through image identifying objects
+ */
+   for (int i = 0; i < numRows; i++) {
+      int j;
+      tmp = idc; idc = idp; idp = tmp;	/* swap ID pointers */
+      memset(idc, '\0', numCols*sizeof(int));
+      
+      imgRowF32 = img->data.F32[i];	// only one of
+      imgRowS32 = img->data.S32[i];	//      these is valid!
+
+      in_span = 0;			/* not in a span */
+      for (j = 0; j < numCols; j++) {
+	 double pixVal = F32 ? imgRowF32[j] : imgRowS32[j];
+	 if (pixVal < threshold) {
+	    if (in_span) {
+	       if(nspan >= size_spans) {
+		  size_spans *= 2;
+		  spans = psRealloc(spans, size_spans*sizeof(WSPAN));
+	       }
+	       spans[nspan].id = in_span;
+	       spans[nspan].y = i;
+	       spans[nspan].x0 = x0;
+	       spans[nspan].x1 = j - 1;
+	       
+	       nspan++;
+
+	       in_span = 0;
+	    }
+	 } else {			/* a pixel to fix */
+	    if(idc[j - 1] != 0) {
+	       id = idc[j - 1];
+	    } else if(idp[j - 1] != 0) {
+	       id = idp[j - 1];
+	    } else if(idp[j] != 0) {
+	       id = idp[j];
+	    } else if(idp[j + 1] != 0) {
+	       id = idp[j + 1];
+	    } else {
+	       id = ++nobj;
+
+	       if(id >= size_aliases) {
+		  size_aliases *= 2;
+		  aliases = psRealloc(aliases, size_aliases*sizeof(int));
+	       }
+	       aliases[id] = id;
+	    }
+
+	    idc[j] = id;
+	    if(!in_span) {
+	       x0 = j; in_span = id;
+	    }
+/*
+ * Do we need to merge ID numbers? If so, make suitable entries in aliases[]
+ */
+	    if(idp[j + 1] != 0 && idp[j + 1] != id) {
+	       aliases[resolve_alias(aliases, idp[j + 1])] =
+						    resolve_alias(aliases, id);
+	       
+	       idc[j] = id = idp[j + 1];
+	    }
+	 }
+      }
+
+      if(in_span) {
+	 if(nspan >= size_spans) {
+	    size_spans *= 2;
+	    spans = psRealloc(spans, size_spans*sizeof(WSPAN));
+	 }
+
+	 assert(nspan < size_spans);	/* we checked for space above */
+	 spans[nspan].id = in_span;
+	 spans[nspan].y = i;
+	 spans[nspan].x0 = x0;
+	 spans[nspan].x1 = j - 1;
+	 
+	 nspan++;
+      }
+   }
+
+   psFree(id_s);
+/*
+ * Resolve aliases; first alias chains, then the IDs in the spans
+ */
+   for (int i = 0; i < nspan; i++) {
+      spans[i].id = resolve_alias(aliases, spans[i].id);
+   }
+
+   psFree(aliases);
+/*
+ * Sort spans by ID, so we can sweep through them once
+ */
+   if(nspan > 0) {
+      qsort(spans, nspan, sizeof(WSPAN), compar);
+   }
+/*
+ * Build pmFootprints from the spans
+ */
+   psArray *footprints = psArrayAlloc(nobj);
+   int n = 0;			// number of pmFootprints
+
+   if(nspan > 0) {
+      id = spans[0].id;
+      i0 = 0;
+      for (int i = 0; i <= nspan; i++) {	/* nspan + 1 to catch last object */
+	 if(i == nspan || spans[i].id != id) {
+	    pmFootprint *fp = pmFootprintAlloc(i - i0, img);
+	    
+	    for(; i0 < i; i0++) {
+		pmFootprintAddSpan(fp, spans[i0].y + row0,
+				   spans[i0].x0 + col0, spans[i0].x1 + col0);
+	    }
+
+	    if (fp->npix < npixMin) {
+	       psFree(fp);
+	    } else {
+	       footprints->data[n++] = fp;
+	    }
+	 }
+	 
+	 id = spans[i].id;
+      }
+   }
+
+   footprints = psArrayRealloc(footprints, n);
+/*
+ * clean up
+ */
+   psFree(spans);
+
+   return footprints;
+}
+
+/************************************************************************************************************/
+/*
+ * A data structure to hold the starting point for a search for pixels above threshold,
+ * used by pmFindFootprintAtPoint
+ *
+ * We don't want to find this span again --- it's already part of the footprint ---
+ * so we set appropriate mask bits
+ */
+//
+// An enum for what we should do with a Startspan
+//
+typedef enum {PM_SSPAN_DOWN = 0,	// scan down from this span
+	      PM_SSPAN_UP,		// scan up from this span
+	      PM_SSPAN_RESTART,		// restart scanning from this span
+	      PM_SSPAN_DONE		// this span is processed
+} PM_SSPAN_DIR;				// How to continue searching
+//
+// An enum for mask's pixel values.  We're looking for pixels that are above threshold, and
+// we keep extra book-keeping information in the PM_SSPAN_STOP plane.  It's simpler to be
+// able to check for 
+//
+enum {
+    PM_SSPAN_INITIAL = 0x0,		// initial state of pixels.
+    PM_SSPAN_DETECTED = 0x1,		// we've seen this pixel
+    PM_SSPAN_STOP = 0x2			// you may stop searching when you see this pixel
+};
+//
+// The struct that remembers how to [re-]start scanning the image for pixels
+//
+typedef struct {
+    const pmSpan *span;			// save the pixel range
+    PM_SSPAN_DIR direction;		// How to continue searching
+    bool stop;				// should we stop searching?
+} Spartspan;
+
+static void startspanFree(Spartspan *sspan) {
+    psFree((void *)sspan->span);
+}
+
+static Spartspan *
+SpartspanAlloc(const pmSpan *span,	// The span in question
+	       psImage *mask,		// Pixels that we've already detected
+	       const PM_SSPAN_DIR dir	// Should we continue searching towards the top of the image?
+    ) {
+    Spartspan *sspan = psAlloc(sizeof(Spartspan));
+    psMemSetDeallocator(sspan, (psFreeFunc)startspanFree);
+
+    sspan->span = psMemIncrRefCounter((void *)span);
+    sspan->direction = dir;
+    sspan->stop = false;
+    
+    if (mask != NULL) {			// remember that we've detected these pixels
+	psMaskType *mpix = &mask->data.PS_TYPE_MASK_DATA[span->y - mask->row0][span->x0 - mask->col0];
+
+	for (int i = 0; i <= span->x1 - span->x0; i++) {
+	    mpix[i] |= PM_SSPAN_DETECTED;
+	    if (mpix[i] & PM_SSPAN_STOP) {
+		sspan->stop = true;
+	    }
+	}
+    }
+    
+    return sspan;
+}
+
+//
+// Add a new Spartspan to an array of Spartspans.  Iff we see a stop bit, return true
+//
+static bool add_startspan(psArray *startspans, // the saved Spartspans
+			  const pmSpan *sp, // the span in question
+			  psImage *mask, // mask of detected/stop pixels
+			  const PM_SSPAN_DIR dir) { // the desired direction to search
+    if (dir == PM_SSPAN_RESTART) {
+	if (add_startspan(startspans, sp, mask,  PM_SSPAN_UP) ||
+	    add_startspan(startspans, sp, NULL, PM_SSPAN_DOWN)) {
+	    return true;
+	}
+    } else {
+	Spartspan *sspan = SpartspanAlloc(sp, mask, dir);
+	if (sspan->stop) {		// we detected a stop bit
+	    psFree(sspan);		// don't allocate new span
+
+	    return true;
+	} else {
+	    psArrayAdd(startspans, 1, sspan);
+	    psFree(sspan);		// as it's now owned by startspans
+	}
+    }
+
+    return false;
+}
+
+/************************************************************************************************************/
+/*
+ * Search the image for pixels above threshold, starting at a single Spartspan.
+ * We search the array looking for one to process; it'd be better to move the
+ * ones that we're done with to the end, but it probably isn't worth it for
+ * the anticipated uses of this routine.
+ *
+ * This is the guts of pmFindFootprintAtPoint
+ */
+static bool do_startspan(pmFootprint *fp, // the footprint that we're building
+			 const psImage *img, // the psImage we're working on
+			 psImage *mask, // the associated masks
+			 const float threshold,	// Threshold
+			 psArray *startspans) {	// specify which span to process next
+    bool F32 = false;			// is this an F32 image?
+    if (img->type.type == PS_TYPE_F32) {
+	F32 = true;
+    } else if (img->type.type == PS_TYPE_S32) {
+	F32 = false;
+    } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
+	psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
+	return NULL;
+    }
+
+    psF32 *imgRowF32 = NULL;		// row pointer if F32
+    psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
+    psMaskType *maskRow = NULL;		//  masks's row pointer
+    
+    const int row0 = img->row0;
+    const int col0 = img->col0;
+    const int numRows = img->numRows;
+    const int numCols = img->numCols;
+    
+    /********************************************************************************************************/
+    
+    Spartspan *sspan = NULL;
+    for (int i = 0; i < startspans->n; i++) {
+	sspan = startspans->data[i];
+	if (sspan->direction != PM_SSPAN_DONE) {
+	    break;
+	}
+	if (sspan->stop) {
+	    break;
+	}
+    }
+    if (sspan == NULL || sspan->direction == PM_SSPAN_DONE) { // no more Spartspans to process
+	return false;
+    }
+    if (sspan->stop) {			// they don't want any more spans processed
+	return false;
+    }
+    /*
+     * Work
+     */
+    const PM_SSPAN_DIR dir = sspan->direction;
+    /*
+     * Set initial span to the startspan
+     */
+    int x0 = sspan->span->x0 - col0, x1 = sspan->span->x1 - col0;
+    /*
+     * Go through image identifying objects
+     */
+    int nx0, nx1 = -1;			// new values of x0, x1
+    const int di = (dir == PM_SSPAN_UP) ? 1 : -1; // how much i changes to get to the next row
+    bool stop = false;			// should I stop searching for spans?
+
+    for (int i = sspan->span->y -row0 + di; i < numRows && i >= 0; i += di) {
+	imgRowF32 = img->data.F32[i];	// only one of
+	imgRowS32 = img->data.S32[i];	//      these is valid!
+	maskRow = mask->data.PS_TYPE_MASK_DATA[i];
+	//
+	// Search left from the pixel diagonally to the left of (i - di, x0). If there's
+	// a connected span there it may need to grow up and/or down, so push it onto
+	// the stack for later consideration
+	//
+	nx0 = -1;
+	for (int j = x0 - 1; j >= -1; j--) {
+	    double pixVal = (j < 0) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+	    if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+		if (j < x0 - 1) {	// we found some pixels above threshold
+		    nx0 = j + 1;
+		}
+		break;
+	    }
+	}
+
+	if (nx0 < 0) {			// no span to the left
+	    nx1 = x0 - 1;		// we're going to resume searching at nx1 + 1
+	} else {
+	    //
+	    // Search right in leftmost span
+	    //
+	    //nx1 = 0;			// make gcc happy
+	    for (int j = nx0 + 1; j <= numCols; j++) {
+		double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+		if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+		    nx1 = j - 1;
+		    break;
+		}
+	    }
+	    
+	    const pmSpan *sp = pmFootprintAddSpan(fp, i + row0, nx0 + col0, nx1 + col0);
+	    
+	    if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
+		stop = true;
+		break;
+	    }
+	}
+	//
+	// Now look for spans connected to the old span.  The first of these we'll
+	// simply process, but others will have to be deferred for later consideration.
+	//
+	// In fact, if the span overhangs to the right we'll have to defer the overhang
+	// until later too, as it too can grow in both directions
+	//
+	// Note that column numCols exists virtually, and always ends the last span; this
+	// is why we claim below that sx1 is always set
+	//
+	bool first = false;		// is this the first new span detected?
+	for (int j = nx1 + 1; j <= x1 + 1; j++) {
+	    double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+	    if (!(maskRow[j] & PM_SSPAN_DETECTED) && pixVal >= threshold) {
+		int sx0 = j++;		// span that we're working on is sx0:sx1
+		int sx1 = -1;		// We know that if we got here, we'll also set sx1
+		for (; j <= numCols; j++) {
+		    double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+		    if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) { // end of span
+			sx1 = j;
+			break;
+		    }
+		}
+		assert (sx1 >= 0);
+
+		const pmSpan *sp;
+		if (first) {
+		    if (sx1 <= x1) {
+			sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
+			if (add_startspan(startspans, sp, mask, PM_SSPAN_DONE)) {
+			    stop = true;
+			    break;
+			}
+		    } else {		// overhangs to right
+			sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, x1 + col0);
+			if (add_startspan(startspans, sp, mask, PM_SSPAN_DONE)) {
+			    stop = true;
+			    break;
+			}
+			sp = pmFootprintAddSpan(fp, i + row0, x1 + 1 + col0, sx1 + col0 - 1);
+			if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
+			    stop = true;
+			    break;
+			}
+		    }
+		    first = false;
+		} else {
+		    sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
+		    if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
+			stop = true;
+			break;
+		    }
+		}
+	    }
+	}
+
+	if (stop || first == false) {	// we're done
+	    break;
+	}
+
+	x0 = nx0; x1 = nx1;
+    }
+    /*
+     * Cleanup
+     */
+
+    sspan->direction = PM_SSPAN_DONE;
+    return stop ? false : true;
+}
+
+/*
+ * Go through an image, starting at (row, col) and assembling all the pixels
+ * that are connected to that point (in a chess kings-move sort of way) into
+ * a pmFootprint.
+ *
+ * This is much slower than pmFindFootprints if you want to find lots of
+ * footprints, but if you only want a small region about a given point it
+ * can be much faster
+ *
+ * N.b. The returned pmFootprint is not in "normal form"; that is the pmSpans
+ * are not sorted by increasing y, x0, x1.  If this matters to you, call
+ * pmFootprintNormalize()
+ */
+pmFootprint *
+pmFindFootprintAtPoint(const psImage *img,	// image to search
+		       const float threshold,	// Threshold
+		       const psArray *peaks, // array of peaks; finding one terminates search for footprint
+		       int row, int col) { // starting position (in img's parent's coordinate system)
+   assert(img != NULL);
+
+   bool F32 = false;			// is this an F32 image?
+   if (img->type.type == PS_TYPE_F32) {
+       F32 = true;
+   } else if (img->type.type == PS_TYPE_S32) {
+       F32 = false;
+   } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
+       psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
+       return NULL;
+   }
+   psF32 *imgRowF32 = NULL;		// row pointer if F32
+   psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
+   
+   const int row0 = img->row0;
+   const int col0 = img->col0;
+   const int numRows = img->numRows;
+   const int numCols = img->numCols;
+/*
+ * Is point in image, and above threshold?
+ */
+   row -= row0; col -= col0;
+   if (row < 0 || row >= numRows ||
+       col < 0 || col >= numCols) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "row/col == (%d, %d) are out of bounds [%d--%d, %d--%d]",
+		row + row0, col + col0, row0, row0 + numRows - 1, col0, col0 + numCols - 1);
+       return NULL;
+   }
+
+   double pixVal = F32 ? img->data.F32[row][col] : img->data.S32[row][col];
+   if (pixVal < threshold) {
+       return pmFootprintAlloc(0, img);
+   }
+   
+   pmFootprint *fp = pmFootprintAlloc(1 + img->numRows/10, img);
+/*
+ * We need a mask for two purposes; to indicate which pixels are already detected,
+ * and to store the "stop" pixels --- those that, once reached, should stop us
+ * looking for the rest of the pmFootprint.  These are generally set from peaks.
+ */
+   psImage *mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+   P_PSIMAGE_SET_ROW0(mask, row0);
+   P_PSIMAGE_SET_COL0(mask, col0);
+   psImageInit(mask, PM_SSPAN_INITIAL);
+   //
+   // Set stop bits from peaks list
+   //
+   assert (peaks == NULL || peaks->n == 0 || psMemCheckPeak(peaks->data[0]));
+   if (peaks != NULL) {
+       for (int i = 0; i < peaks->n; i++) {
+	   pmPeak *peak = peaks->data[i];
+	   mask->data.PS_TYPE_MASK_DATA[peak->y - mask->row0][peak->x - mask->col0] |= PM_SSPAN_STOP;
+       }
+   }
+/*
+ * Find starting span passing through (row, col)
+ */
+   psArray *startspans = psArrayAllocEmpty(1); // spans where we have to restart the search
+   
+   imgRowF32 = img->data.F32[row];	// only one of
+   imgRowS32 = img->data.S32[row];	//      these is valid!
+   psMaskType *maskRow = mask->data.PS_TYPE_MASK_DATA[row];
+   {
+       int i;
+       for (i = col; i >= 0; i--) {
+	   pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
+	   if ((maskRow[i] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+	       break;
+	   }
+       }
+       int i0 = i;
+       for (i = col; i < numCols; i++) {
+	   pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
+	   if ((maskRow[i] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+	       break;
+	   }
+       }
+       int i1 = i;
+       const pmSpan *sp = pmFootprintAddSpan(fp, row + row0, i0 + col0 + 1, i1 + col0 - 1);
+
+       (void)add_startspan(startspans, sp, mask, PM_SSPAN_RESTART);
+   }
+   /*
+    * Now workout from those Spartspans, searching for pixels above threshold
+    */
+   while (do_startspan(fp, img, mask, threshold, startspans)) continue;
+   /*
+    * Cleanup
+    */
+   psFree(mask);
+   psFree(startspans);			// restores the image pixel
+
+   return fp;				// pmFootprint really
+}
+
+/************************************************************************************************************/
+/*
+ * Worker routine for the pmSetFootprintArrayIDs/pmSetFootprintID (and pmMergeFootprintArrays)
+ */
+static void
+set_footprint_id(psImage *idImage,	// the image to set
+		 const pmFootprint *fp, // the footprint to insert
+		 const int id) {	// the desired ID
+   const int col0 = fp->region.x0;
+   const int row0 = fp->region.y0;
+
+   for (int j = 0; j < fp->spans->n; j++) {
+       const pmSpan *span = fp->spans->data[j];
+       psS32 *imgRow = idImage->data.S32[span->y - row0];
+       for(int k = span->x0 - col0; k <= span->x1 - col0; k++) {
+	   imgRow[k] += id;
+       }
+   }
+}
+
+static void
+set_footprint_array_ids(psImage *idImage,
+			const psArray *footprints, // the footprints to insert
+			const bool relativeIDs) { // show IDs starting at 0, not pmFootprint->id
+   int id = 0;				// first index will be 1
+   for (int i = 0; i < footprints->n; i++) {
+       const pmFootprint *fp = footprints->data[i];
+       if (relativeIDs) {
+	   id++;
+       } else {
+	   id = fp->id;
+       }
+       
+       set_footprint_id(idImage, fp, id);
+   }
+}
+
+/*
+ * Set an image to the value of footprint's ID whever they may fall
+ */
+psImage *pmSetFootprintArrayIDs(const psArray *footprints, // the footprints to insert
+				const bool relativeIDs) { // show IDs starting at 1, not pmFootprint->id
+   assert (footprints != NULL);
+
+   if (footprints->n == 0) {
+       psError(PS_ERR_BAD_PARAMETER_SIZE, true, "You didn't provide any footprints");
+       return NULL;
+   }
+   const pmFootprint *fp = footprints->data[0];
+   assert(pmFootprintTest((const psPtr)fp));
+   const int numCols = fp->region.x1 - fp->region.x0 + 1;
+   const int numRows = fp->region.y1 - fp->region.y0 + 1;
+   const int col0 = fp->region.x0;
+   const int row0 = fp->region.y0;
+   assert (numCols >= 0 && numRows >= 0);
+   
+   psImage *idImage = psImageAlloc(numCols, numRows, PS_TYPE_S32);
+   P_PSIMAGE_SET_ROW0(idImage, row0);
+   P_PSIMAGE_SET_COL0(idImage, col0);
+   psImageInit(idImage, 0);
+   /*
+    * do the work
+    */
+   set_footprint_array_ids(idImage, footprints, relativeIDs);
+
+   return idImage;
+   
+}
+
+/*
+ * Set an image to the value of footprint's ID whever they may fall
+ */
+psImage *pmSetFootprintID(const pmFootprint *fp, // the footprint to insert
+			  const int id) {	// the desired ID
+   assert(fp != NULL && pmFootprintTest((const psPtr)fp));
+   const int numCols = fp->region.x1 - fp->region.x0 + 1;
+   const int numRows = fp->region.y1 - fp->region.y0 + 1;
+   const int col0 = fp->region.x0;
+   const int row0 = fp->region.y0;
+   assert (numCols >= 0 && numRows >= 0);
+   
+   psImage *idImage = psImageAlloc(numCols, numRows, PS_TYPE_S32);
+   P_PSIMAGE_SET_ROW0(idImage, row0);
+   P_PSIMAGE_SET_COL0(idImage, col0);
+   psImageInit(idImage, 0);
+   /*
+    * do the work
+    */
+   set_footprint_id(idImage, fp, id);
+
+   return idImage;
+   
+}
+
+/************************************************************************************************************/
+/*
+ * Grow a psArray of pmFootprints isotropically by r pixels, returning a new psArray of new pmFootprints
+ */
+psArray *pmGrowFootprintArray(const psArray *footprints, // footprints to grow
+			      int r) {	// how much to grow each footprint
+    assert (footprints->n == 0 || pmFootprintTest(footprints->data[0]));
+
+    if (footprints->n == 0) {		// we don't know the size of the footprint's region
+	return psArrayAlloc(0);
+    }
+    /*
+     * We'll insert the footprints into an image, then convolve with a disk,
+     * then extract a footprint from the result --- this is magically what we want.
+     */
+    psImage *idImage = pmSetFootprintArrayIDs(footprints, true);
+    if (r <= 0) {
+	r = 1;				// r == 1 => no grow
+    }
+    psKernel *circle = psKernelAlloc(-r, r, -r, r);
+    assert (circle->image->numRows == 2*r + 1 && circle->image->numCols == circle->image->numRows);
+    for (int i = 0; i <= r; i++) {
+	for (int j = 0; j <= r; j++) {
+	    if (i*i + j*j <= r*r) {
+		circle->kernel[i][j] = 
+		    circle->kernel[i][-j] = 
+		    circle->kernel[-i][j] = 
+		    circle->kernel[-i][-j] = 1;
+	    }
+	}
+    }
+
+    psImage *grownIdImage = psImageConvolveDirect(NULL, idImage, circle); // Here's the actual grow step
+    psFree(circle);	
+
+    psArray *grown = pmFindFootprints(grownIdImage, 0.5, 1); // and here we rebuild the grown footprints
+    assert (grown != NULL);
+    psFree(idImage); psFree(grownIdImage);
+    /*
+     * Now assign the peaks appropriately.  We could do this more efficiently
+     * using grownIdImage (which we just freed), but this is easy and probably fast enough
+     */
+    const psArray *peaks = pmFootprintArrayToPeaks(footprints);
+    pmPeaksAssignToFootprints(grown, peaks);
+    psFree((psArray *)peaks);
+
+    return grown;
+    
+}
+
+/************************************************************************************************************/
+/*
+ * Merge together two psArrays of pmFootprints neither of which is damaged.
+ *
+ * The returned psArray may contain elements of the inital psArrays (with
+ * their reference counters suitable incremented)
+ */
+psArray *pmMergeFootprintArrays(const psArray *footprints1, // one set of footprints
+				const psArray *footprints2, // the other set
+				const int includePeaks) { // which peaks to set? 0x1 => footprints1, 0x2 => 2
+    assert (footprints1->n == 0 || pmFootprintTest(footprints1->data[0]));
+    assert (footprints2->n == 0 || pmFootprintTest(footprints2->data[0]));
+
+    if (footprints1->n == 0 || footprints2->n == 0) {		// nothing to do but put copies on merged
+	const psArray *old = (footprints1->n == 0) ? footprints2 : footprints1;
+
+	psArray *merged = psArrayAllocEmpty(old->n);
+	for (int i = 0; i < old->n; i++) {
+	    psArrayAdd(merged, 1, old->data[i]);
+	}
+	
+	return merged;
+    }
+    /*
+     * We have real work to do as some pmFootprints in footprints2 may overlap
+     * with footprints1
+     */
+    {
+	pmFootprint *fp1 = footprints1->data[0];
+	pmFootprint *fp2 = footprints2->data[0];
+	if (fp1->region.x0 != fp2->region.x0 ||
+	    fp1->region.x1 != fp2->region.x1 ||
+	    fp1->region.y0 != fp2->region.y0 ||
+	    fp1->region.y1 != fp2->region.y1) {
+	    psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+		    "The two pmFootprint arrays correspnond to different-sized regions");
+	    return NULL;
+	}
+    }
+    /*
+     * We'll insert first one set of footprints then the other into an image, then
+     * extract a footprint from the result --- this is magically what we want.
+     */
+    psImage *idImage = pmSetFootprintArrayIDs(footprints1, true);
+    set_footprint_array_ids(idImage, footprints2, true);
+
+    psArray *merged = pmFindFootprints(idImage, 0.5, 1);
+    assert (merged != NULL);
+    psFree(idImage);
+    /*
+     * Now assign the peaks appropriately.  We could do this more efficiently
+     * using idImage (which we just freed), but this is easy and probably fast enough
+     */ 
+    if (includePeaks & 0x1) {
+	const psArray *peaks = pmFootprintArrayToPeaks(footprints1);
+	pmPeaksAssignToFootprints(merged, peaks);
+	psFree((psArray *)peaks);
+    }
+
+    if (includePeaks & 0x2) {
+	const psArray *peaks = pmFootprintArrayToPeaks(footprints2);
+	pmPeaksAssignToFootprints(merged, peaks);
+	psFree((psArray *)peaks);
+    }
+    
+    return merged;
+}
+
+/************************************************************************************************************/
+/*
+ * Given a psArray of pmFootprints and another of pmPeaks, assign the peaks to the
+ * footprints in which that fall; if they _don't_ fall in a footprint, add a suitable
+ * one to the list.
+ */
+psErrorCode
+pmPeaksAssignToFootprints(psArray *footprints,	// the pmFootprints
+			  const psArray *peaks) { // the pmPeaks
+    assert (footprints != NULL);
+    assert (footprints->n == 0 || pmFootprintTest(footprints->data[0]));
+    assert (peaks != NULL);
+    assert (peaks->n == 0 || psMemCheckPeak(peaks->data[0]));
+    
+    if (footprints->n == 0) {
+	if (peaks->n > 0) {
+	    return psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Your list of footprints is empty");
+	}
+	return PS_ERR_NONE;
+    }
+    /*
+     * Create an image filled with the object IDs, and use it to assign pmPeaks to the
+     * objects
+     */
+    psImage *ids = pmSetFootprintArrayIDs(footprints, true);
+    assert (ids != NULL);
+    assert (ids->type.type == PS_TYPE_S32);
+    const int row0 = ids->row0;
+    const int col0 = ids->col0;
+    const int numRows = ids->numRows;
+    const int numCols = ids->numCols;
+
+    for (int i = 0; i < peaks->n; i++) {
+	pmPeak *peak = peaks->data[i];
+	const int x = peak->x - col0;
+	const int y = peak->y - row0;
+	
+	assert (x >= 0 && x < numCols && y >= 0 && y < numRows);
+	int id = ids->data.S32[y][x - col0];
+
+	if (id == 0) {			// peak isn't in a footprint, so make one for it
+	    pmFootprint *nfp = pmFootprintAlloc(1, ids);
+	    pmFootprintAddSpan(nfp, y, x, x);
+	    psArrayAdd(footprints, 1, nfp);
+	    psFree(nfp);
+	    id = footprints->n;
+	}
+
+	assert (id >= 1 && id <= footprints->n);
+	pmFootprint *fp = footprints->data[id - 1];
+	psArrayAdd(fp->peaks, 5, peak);
+    }
+    
+    psFree(ids);
+    //
+    // Make sure that peaks within each footprint are sorted and unique
+    //
+    for (int i = 0; i < footprints->n; i++) {
+	pmFootprint *fp = footprints->data[i];
+        fp->peaks = psArraySort(fp->peaks, pmPeakSortBySN);
+
+	for (int j = 1; j < fp->peaks->n; j++) { // check for duplicates
+	    if (fp->peaks->data[j] == fp->peaks->data[j-1]) {
+		(void)psArrayRemoveIndex(fp->peaks, j);
+		j--;			// we moved everything down one
+	    }
+	}
+    }
+
+    return PS_ERR_NONE;
+}
+
+/************************************************************************************************************/
+ /*
+  * Examine the peaks in a pmFootprint, and throw away the ones that are not sufficiently
+  * isolated.  More precisely, for each peak find the highest coll that you'd have to traverse
+  * to reach a still higher peak --- and if that coll's more than nsigma DN below your
+  * starting point, discard the peak.
+  */
+psErrorCode pmFootprintCullPeaks(const psImage *img, // the image wherein lives the footprint
+				 const psImage *weight,	// corresponding variance image
+				 pmFootprint *fp, // Footprint containing mortal peaks
+				 const float nsigma_delta, // how many sigma above local background a peak
+				 	// needs to be to survive
+				 const float min_threshold) { // minimum permitted coll height
+    assert (img != NULL); assert (img->type.type == PS_TYPE_F32);
+    assert (weight != NULL); assert (weight->type.type == PS_TYPE_F32);
+    assert (img->row0 == weight->row0 && img->col0 == weight->col0);
+    assert (fp != NULL);
+
+    if (fp->peaks == NULL || fp->peaks->n == 0) { // nothing to do
+	return PS_ERR_NONE;
+    }
+
+    psRegion subRegion;			// desired subregion; 1 larger than bounding box (grr)
+    subRegion.x0 = fp->bbox.x0; subRegion.x1 = fp->bbox.x1 + 1;
+    subRegion.y0 = fp->bbox.y0; subRegion.y1 = fp->bbox.y1 + 1;
+    const psImage *subImg = psImageSubset((psImage *)img, subRegion);
+    const psImage *subWt = psImageSubset((psImage *)weight, subRegion);
+    assert (subImg != NULL && subWt != NULL);
+    //
+    // We need a psArray of peaks brighter than the current peak.  We'll fake this
+    // by reusing the fp->peaks but lying about n.
+    //
+    // We do this for efficiency (otherwise I'd need two peaks lists), and we are
+    // rather too chummy with psArray in consequence.  But it works.
+    //
+    psArray *brightPeaks = psArrayAlloc(0);
+    psFree(brightPeaks->data);
+    brightPeaks->data = psMemIncrRefCounter(fp->peaks->data);// use the data from fp->peaks
+    //
+    // The brightest peak is always safe; go through other peaks trying to cull them
+    //
+    for (int i = 1; i < fp->peaks->n; i++) { // n.b. fp->peaks->n can change within the loop
+	const pmPeak *peak = fp->peaks->data[i];
+	int x = peak->x - subImg->col0;
+	int y = peak->y - subImg->row0;
+	//
+	// Find the level nsigma below the peak that must separate the peak
+	// from any of its friends
+	//
+	assert (x >= 0 && x < subImg->numCols && y >= 0 && y < subImg->numRows);
+	const float stdev = sqrt(subWt->data.F32[y][x]);
+	float threshold = subImg->data.F32[y][x] - nsigma_delta*stdev;
+	if (isnan(threshold) || threshold < min_threshold) {
+#if 1					// min_threshold is assumed to be below the detection threshold,
+					// so all the peaks are pmFootprint, and this isn't the brightest
+	    (void)psArrayRemoveIndex(fp->peaks, i);
+	    i--;			// we moved everything down one
+	    continue;
+#else
+#error n.b. We will be running LOTS of checks at this threshold, so only find the footprint once
+	    threshold = min_threshold;
+#endif
+	}
+	if (threshold > subImg->data.F32[y][x]) {
+	    threshold = subImg->data.F32[y][x] - 10*FLT_EPSILON;
+	}
+
+	const int peak_id = 1;		// the ID for the peak of interest
+	brightPeaks->n = i;		// only stop at a peak brighter than we are
+	pmFootprint *peakFootprint = pmFindFootprintAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
+	brightPeaks->n = 0;		// don't double free
+	psImage *idImg = pmSetFootprintID(peakFootprint, peak_id);
+	psFree(peakFootprint);
+
+	int j;
+	for (j = 0; j < i; j++) {
+	    const pmPeak *peak2 = fp->peaks->data[j];
+	    int x2 = peak2->x - subImg->col0;
+	    int y2 = peak2->y - subImg->row0;
+	    const int peak2_id = idImg->data.S32[y2][x2]; // the ID for some other peak
+
+	    if (peak2_id == peak_id) {	// There's a brighter peak within the footprint above
+		;			// threshold; so cull our initial peak
+		(void)psArrayRemoveIndex(fp->peaks, i);
+		i--;			// we moved everything down one
+		break;
+	    }
+	}
+	if (j == i) {
+	    j++;
+	}
+
+	psFree(idImg);
+    }
+
+    brightPeaks->n = 0; psFree(brightPeaks);
+    psFree((psImage *)subImg);
+    psFree((psImage *)subWt);
+
+    return PS_ERR_NONE;
+}
+
+/*
+ * Cull an entire psArray of pmFootprints
+ */
+psErrorCode
+pmFootprintArrayCullPeaks(const psImage *img, // the image wherein lives the footprint
+			  const psImage *weight,	// corresponding variance image
+			  psArray *footprints, // array of pmFootprints
+			  const float nsigma_delta, // how many sigma above local background a peak
+    					// needs to be to survive
+			  const float min_threshold) { // minimum permitted coll height
+    for (int i = 0; i < footprints->n; i++) {
+	pmFootprint *fp = footprints->data[i];
+	if (pmFootprintCullPeaks(img, weight, fp, nsigma_delta, min_threshold) != PS_ERR_NONE) {
+	    return psError(PS_ERR_UNKNOWN, false, "Culling pmFootprint %d", fp->id);
+	}
+    }
+    
+    return PS_ERR_NONE;
+}
+
+/************************************************************************************************************/
+/*
+ * Extract the peaks in a psArray of pmFootprints, returning a psArray of pmPeaks
+ */
+psArray *pmFootprintArrayToPeaks(const psArray *footprints) {
+   assert(footprints != NULL);
+   assert(footprints->n == 0 || pmFootprintTest(footprints->data[0]));
+
+   int npeak = 0;
+   for (int i = 0; i < footprints->n; i++) {
+      const pmFootprint *fp = footprints->data[i];
+      npeak += fp->peaks->n;
+   }
+
+   psArray *peaks = psArrayAllocEmpty(npeak);
+   
+   for (int i = 0; i < footprints->n; i++) {
+      const pmFootprint *fp = footprints->data[i];
+      for (int j = 0; j < fp->peaks->n; j++) {
+	 psArrayAdd(peaks, 1, fp->peaks->data[j]);
+      }
+   }
+
+   return peaks;
+}
+
+void pmDetectionsFree (pmDetections *detections) {
+
+  if (!detections) return;
+
+  psFree (detections->footprints);
+  psFree (detections->peaks);
+  psFree (detections->oldPeaks);
+  return;
+}
+
+// generate a pmDetections container with empty (allocated) footprints and peaks containers
+pmDetections *pmDetectionsAlloc() {
+
+    pmDetections *detections = (pmDetections *)psAlloc(sizeof(pmDetections));
+    psMemSetDeallocator(detections, (psFreeFunc) pmDetectionsFree);
+
+    detections->footprints = NULL;
+    detections->peaks      = NULL;
+    detections->oldPeaks   = NULL;
+    detections->last       = 0;
+
+    return (detections);
+}
+
+/************************************************************************************************************/
+/*
+ * Cull a set of peaks contained in a psArray of pmFootprints
+ */
+psErrorCode
+psphotCullPeaks(const psImage *image,   // the image wherein lives the footprint
+                const psImage *weight,  // corresponding variance image
+                const psMetadata *recipe,
+                psArray *footprints) {  // array of pmFootprints
+    bool status = false;
+    float nsigma_delta = psMetadataLookupF32(&status, recipe, "FOOTPRINT_CULL_NSIGMA_DELTA");
+    if (!status) {
+        nsigma_delta = 0; // min.
+    }
+    float nsigma_min = psMetadataLookupF32(&status, recipe, "FOOTPRINT_CULL_NSIGMA_MIN");
+    if (!status) {
+        nsigma_min = 0;
+    }
+    const float skyStdev = psMetadataLookupF32(NULL, recipe, "SKY_STDEV");
+
+    return pmFootprintArrayCullPeaks(image, weight, footprints,
+                                     nsigma_delta, nsigma_min*skyStdev);
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/pmFootprint.h
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/pmFootprint.h	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/pmFootprint.h	(revision 22147)
@@ -0,0 +1,64 @@
+#if !defined(PM_FOOTPRINT_H)
+#define PM_FOOTPRINT_H
+//
+// Describe a segment of an image
+//
+typedef struct {
+    int y;                              //!< Row that span's in
+    int x0;                             //!< Starting column (inclusive)
+    int x1;                             //!< Ending column (inclusive)
+} pmSpan;
+
+pmSpan *pmSpanAlloc(int y, int x1, int x2);
+bool pmSpanTest(const psPtr ptr);
+int pmSpanSortByYX (const void **a, const void **b);
+
+typedef struct {
+    const int id;                       //!< unique ID
+    int npix;                           //!< number of pixels in this pmFootprint
+    psArray *spans;                     //!< the pmSpans
+    psRegion bbox;                      //!< the pmFootprint's bounding box
+    psArray *peaks;                     //!< the peaks lying in this footprint
+    psRegion region;   //!< A region describing the psImage the footprints live in
+    bool normalized;                    //!< Are the spans sorted? 
+} pmFootprint;
+
+pmFootprint *pmFootprintAlloc(int nspan, const psImage *img);
+bool pmFootprintTest(const psPtr ptr);
+
+pmFootprint *pmFootprintNormalize(pmFootprint *fp);
+int pmFootprintSetNpix(pmFootprint *fp);
+void pmFootprintSetBBox(pmFootprint *fp);
+psArray *pmFindFootprints(const psImage *img, const float threshold, const int npixMin);
+pmFootprint *pmFindFootprintAtPoint(const psImage *img,
+                                    const float threshold,
+                                    const psArray *peaks,
+                                    int row, int col);
+
+psArray *pmGrowFootprintArray(const psArray *footprints, int r);
+psArray *pmMergeFootprintArrays(const psArray *footprints1, const psArray *footprints2,
+                                const int includePeaks);
+
+psImage *pmSetFootprintArrayIDs(const psArray *footprints, const bool relativeIDs);
+psImage *pmSetFootprintID(const pmFootprint *fp, const int id);
+
+psErrorCode pmPeaksAssignToFootprints(psArray *footprints, const psArray *peaks);
+
+psErrorCode pmFootprintArrayCullPeaks(const psImage *img, const psImage *weight, psArray *footprints,
+                                 const float nsigma, const float threshold_min);
+psErrorCode pmFootprintCullPeaks(const psImage *img, const psImage *weight, pmFootprint *fp,
+                                 const float nsigma, const float threshold_min);
+
+psArray *pmFootprintArrayToPeaks(const psArray *footprints);
+
+typedef struct {
+  psArray *footprints;	      // collection of footprints in the image
+  psArray *peaks;	      // collection of all peaks contained by the footprints
+  psArray *oldPeaks;	      // collection of all peaks previously found
+  int last;
+} pmDetections;
+
+pmDetections *pmDetectionsAlloc ();
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphot.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphot.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphot.c	(revision 22147)
@@ -0,0 +1,42 @@
+# include "psphotStandAlone.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: psphot [-file image(s)] [-list imagelist] (output)\n");
+    exit (PS_EXIT_CONFIG_ERROR);
+}
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();			// register psModule's error codes/messages
+    psphotErrorRegister();              // register our error codes/messages
+    psphotModelClassInit ();            // load implementation-specific models
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotArguments (argc, argv);
+    if (!config) {
+	psErrorStackPrint(stderr, "Error reading arguments\n");
+	usage ();
+    }
+
+    // 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)) {
+        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: /branches/eam_branches/eam_branch_20080229/psphot/src/psphot.h
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphot.h	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphot.h	(revision 22147)
@@ -0,0 +1,176 @@
+/* This file defines the library functions available to external programs.  It must be included
+ * by programs which are compiled against psphot functions.
+ */
+
+#ifndef PSPHOT_H
+#define PSPHOT_H
+
+#include <psmodules.h>
+
+#include "psphotErrorCodes.h"
+#include "pmFootprint.h"
+
+#define PSPHOT_RECIPE "PSPHOT" // Name of the recipe to use
+
+// top-level psphot functions
+const char     *psphotCVSName(void);
+psString        psphotVersion(void);
+psString        psphotVersionLong(void);
+
+bool            psphotModelTest (pmConfig *config, const pmFPAview *view, psMetadata *recipe);
+bool            psphotReadout (pmConfig *config, const pmFPAview *view);
+bool            psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmDetections *detections, pmPSF *psf, psArray *sources);
+bool            psphotDefineFiles (pmConfig *config, pmFPAfile *input);
+
+// XXX test functions
+psArray        *psphotFakeSources ();
+
+// psphotReadout functions
+bool            psphotModelBackground (pmConfig *config, const pmFPAview *view, const char *filename);
+bool            psphotSubtractBackground (pmConfig *config, const pmFPAview *view, const char *filename) ;
+pmDetections   *psphotFindDetections (pmDetections *detections, pmReadout *readout, psMetadata *recipe);
+
+psArray        *psphotSourceStats (pmReadout *readout, psMetadata *recipe, pmDetections *detections);
+bool            psphotRoughClass (psArray *sources, psMetadata *recipe, const bool findPsfClump);
+bool            psphotBasicDeblend (psArray *sources, psMetadata *recipe);
+pmPSF          *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf);
+bool            psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources);
+bool            psphotFitSourcesLinear (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final);
+bool            psphotGuessModels (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotReplaceUnfit (psArray *sources, psMaskType maskVal);
+bool            psphotReplaceAll (psArray *sources, psMetadata *recipe);
+bool            psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotMagnitudes (psArray *sources, psMetadata *recipe, pmPSF *psf, pmReadout *background);
+bool            psphotSkyReplace (pmConfig *config, const pmFPAview *view);
+bool            psphotExtendedSources (pmReadout *readout, psArray *sources, psMetadata *recipe);
+
+// used by psphotFindDetections
+psArray        *psphotFindPeaks (pmReadout *readout, psMetadata *recipe, const bool returnFootprints, const int pass, psMaskType maskVal);
+psErrorCode     psphotCullPeaks(const psImage *img, const psImage *weight, const psMetadata *recipe, psArray *footprints);
+
+// used by ApResid
+bool            psphotMagErrorScale (float *errorScale, float *errorFloor, psVector *dMag, psVector *dap, psVector *mask, int nGroup);
+bool            psphotApResidTrend (pmReadout *readout, pmPSF *psf, int Npsf, int scale, float *errorScale, float *errorFloor, psVector *mask, psVector *xPos, psVector *yPos, psVector *apResid, psVector *dMag);
+
+// basic support functions
+void            psphotModelClassInit (void);
+bool            psphotGrowthCurve (pmReadout *readout, pmPSF *psf, bool ignore, psMaskType maskVal);
+bool            psphotSetMaskAndWeight (pmConfig *config, pmReadout *readout, psMetadata *recipe);
+void            psphotSourceFreePixels (psArray *sources);
+
+// functions to set the correct source pixels
+bool            psphotInitRadiusPSF (const psMetadata *recipe, const pmModelType type);
+bool            psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model);
+bool            psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR);
+bool            psphotInitRadiusEXT (psMetadata *recipe, pmModelType type);
+bool            psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model);
+
+// output functions
+bool            psphotAddPhotcode (psMetadata *recipe, pmConfig *config, const pmFPAview *view);
+bool            psphotDumpMoments (psMetadata *recipe, psArray *sources);
+psMetadata     *psphotDefineHeader (psMetadata *recipe);
+int             psphotSaveImage (psMetadata *header, psImage *image, char *filename);
+bool            psphotDumpConfig (pmConfig *config);
+pmReadout      *psphotSelectBackground (pmConfig *config, const pmFPAview *view, const bool stdev);
+
+// PSF / DBL / EXT evaluation functions
+bool            psphotEvalPSF (pmSource *source, pmModel *model);
+bool            psphotEvalDBL (pmSource *source, pmModel *model);
+bool            psphotEvalEXT (pmSource *source, pmModel *model);
+
+//  functions to support the source fitting process
+bool            psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout);
+bool            psphotInitLimitsEXT (psMetadata *recipe);
+bool            psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal);
+bool            psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf, psMaskType maskVal);
+bool            psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal);
+pmModel        *psphotFitEXT (pmReadout *readout, pmSource *source, psMaskType maskVal);
+psArray        *psphotFitDBL (pmReadout *readout, pmSource *source, psMaskType maskVal);
+
+// functions to support simultaneous multi-source fitting
+bool            psphotFitSet (pmSource *oneSrc, pmModel *oneModel, char *fitset, pmSourceFitMode mode, psMaskType maskVal);
+
+// plotting functions (available if libkapa is installed)
+bool            psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources);
+bool            psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources);
+bool            psphotFitInit ();
+bool            psphotFitSummary ();
+bool            psphotMergeSources (psArray *oldSources, psArray *newSources);
+bool            psphotLoadExtSources (pmConfig *config, const pmFPAview *view, psArray *sources);
+pmPSF          *psphotLoadPSF (pmConfig *config, const pmFPAview *view, psMetadata *recipe);
+bool            psphotSetHeaderNstars (psMetadata *recipe, psArray *sources);
+bool            psphotAddNoise (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotAddOrSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe, bool add);
+bool            psphotRadialPlot (int *kapa, const char *filename, pmSource *source);
+bool            psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotMosaicSubimage (psImage *outImage, pmSource *source, int Xo, int Yo, int DX, int DY);
+
+bool            psphotAddWithTest (pmSource *source, bool useState, psMaskType maskVal);
+bool            psphotSubWithTest (pmSource *source, bool useState, psMaskType maskVal);
+bool            psphotSetState (pmSource *source, bool curState, psMaskType maskVal);
+bool            psphotDeblendSatstars (psArray *sources, psMetadata *recipe);
+bool            psphotSourceSize (pmReadout *readout, psArray *sources, psMetadata *recipe, long first);
+
+bool            psphotMakeResiduals (psArray *sources, psMetadata *recipe, pmPSF *psf, psMaskType maskVal);
+
+bool            psphotPSFConvModel (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+psKernel       *psphotKernelFromPSF (pmSource *source, int nPix);
+
+bool            psphotRadialProfile (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool 		psphotPetrosian (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool 		psphotIsophotal (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool 		psphotAnnuli (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool 		psphotKron (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+
+// structures & functions to support psf-convolved model fitting
+
+// pmPCMData : PSF Convolved Model data storage structure
+typedef struct {
+    psImage *model;
+    psArray *dmodels;
+    psImage *modelConv;
+    psArray *dmodelsConv;
+} pmPCMData;
+
+
+// psf-convolved model fitting
+bool psphotModelWithPSF_LMM (
+    psMinimization *min,
+    psImage *covar,
+    psVector *params,
+    psMinConstraint *constraint,
+    pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func);
+
+psF32 psphotModelWithPSF_SetABX(
+    psImage  *alpha,
+    psVector *beta,
+    const psVector *params,
+    const psVector *paramMask,
+    pmPCMData *pcm,
+    const pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func);
+
+pmPCMData *pmPCMDataAlloc (
+    const psVector *params,
+    const psVector *paramMask,
+    pmSource *source);
+
+psImage *pmPCMDataSaveImage (pmPCMData *pcm);
+
+int psphotKapaOpen ();
+bool psphotKapaClose ();
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy);
+bool psphotDiagnosticPlots (pmConfig *config, char *name, ...);
+
+bool psphotMakeFluxScale (psImage *image, psMetadata *recipe, pmPSF *psf);
+
+bool psphotCheckStarDistribution (psArray *sources, psArray *stars, pmPSFOptions *options);
+int psphotSupplementStars (psArray *stars, psArray *sources, psImageBinning *binning, int ix, int iy);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotAddNoise.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotAddNoise.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotAddNoise.c	(revision 22147)
@@ -0,0 +1,96 @@
+# include "psphotInternal.h"
+
+bool psphotAddNoise (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+  return psphotAddOrSubNoise (readout, sources, recipe, true);
+}
+
+bool psphotSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+  return psphotAddOrSubNoise (readout, sources, recipe, false);
+}
+
+bool psphotAddOrSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe, bool add) {
+
+    bool status = false;
+    psEllipseShape oldshape;
+    psEllipseShape newshape;
+    psEllipseAxes axes;
+
+    PS_ASSERT (readout, false);
+    PS_ASSERT (readout->parent, false);
+    PS_ASSERT (readout->parent->concepts, false);
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // increase variance by factor*(object noise):
+    // weight = flux/gain + rn^2/g^2
+    // we are adding factor*flux/gain
+
+    float FACTOR = psMetadataLookupF32 (&status, recipe, "NOISE.FACTOR");
+    PS_ASSERT (status, false);
+    float SIZE = psMetadataLookupF32 (&status, recipe, "NOISE.SIZE");
+    PS_ASSERT (status, false);
+
+    if (SIZE <= 0) {
+       return true;
+    }
+
+    float GAIN = psMetadataLookupF32(&status, readout->parent->concepts, "CELL.GAIN"); // Cell gain
+    PS_ASSERT (status, false);
+
+    FACTOR /= GAIN;
+
+    // loop over all source
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip sources which were not subtracted
+        if (!(source->mode & PM_SOURCE_MODE_SUBTRACTED)) continue;
+
+        // select appropriate model
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL) continue;  // model must be defined
+
+        if (add) {
+            psTrace ("psphot", 4, "adding noise for object at %f,%f\n", model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS]);
+        } else {
+            psTrace ("psphot", 4, "remove noise for object at %f,%f\n", model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS]);
+        }
+
+        psF32 *PAR = model->params->data.F32;
+
+        // save original values
+        float oldI0  = PAR[PM_PAR_I0];
+        oldshape.sx  = PAR[PM_PAR_SXX];
+        oldshape.sy  = PAR[PM_PAR_SYY];
+        oldshape.sxy = PAR[PM_PAR_SXY];
+
+        // increase size and height of source
+        axes = psEllipseShapeToAxes (oldshape, 20.0);
+        axes.major *= SIZE;
+        axes.minor *= SIZE;
+        newshape = psEllipseAxesToShape (axes);
+        PAR[PM_PAR_I0]  = FACTOR*PS_SQR(oldI0);
+        PAR[PM_PAR_SXX] = newshape.sx;
+        PAR[PM_PAR_SYY] = newshape.sy;
+        PAR[PM_PAR_SXY] = newshape.sxy;
+
+        // XXX if we use pmSourceOp, the size (and possibly Io) will not be respected
+        pmSourceOp (source, PM_MODEL_OP_FULL | PM_MODEL_OP_NOISE, add, maskVal, 0, 0);
+
+        // restore original values
+        PAR[PM_PAR_I0]  = oldI0;
+        PAR[PM_PAR_SXX] = oldshape.sx;
+        PAR[PM_PAR_SYY] = oldshape.sy;
+        PAR[PM_PAR_SXY] = oldshape.sxy;
+    }
+    if (add) {
+        psLogMsg ("psphot.noise", PS_LOG_INFO, "add noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    } else {
+        psLogMsg ("psphot.noise", PS_LOG_INFO, "sub noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    }
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotAnnuli.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotAnnuli.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotAnnuli.c	(revision 22147)
@@ -0,0 +1,59 @@
+# include "psphot.h"
+
+bool psphotAnnuli (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  bool status;
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *weight = source->extpars->profile->weight;
+  psVector *flux = source->extpars->profile->flux;
+
+  // XXX how do I define the radii?  we can put a vector in the recipe...
+  // radialBins defines the bounds or start and stop (we can skip some that way...
+  psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
+  psVector *radialBinsUpper = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
+  assert (radialBinsLower->n == radialBinsUpper->n);
+
+  psVector *fluxValues = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *fluxSquare = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *fluxWeight = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *pixelCount = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVectorInit (fluxValues, 0.0);
+  psVectorInit (fluxSquare, 0.0);
+  psVectorInit (fluxWeight, 0.0);
+  psVectorInit (pixelCount, 0.0);
+
+  // XXX this code assumes the radii are in pixels.  convert from arcsec with plate scale
+  // XXX assume the annulii above are not overlapping?  much faster...
+  // XXX this might be must faster in the reverse order: loop over annulii and use disection to 
+  // skip to the start of the annulus.
+  for (int i = 0; i < flux->n; i++) {
+    for (int j = 0; j < radialBinsLower->n; j++) {
+      if (radius->data.F32[i] < radialBinsLower->data.F32[j]) continue;
+      if (radius->data.F32[i] > radialBinsUpper->data.F32[j]) continue;
+      fluxValues->data.F32[j] += flux->data.F32[i];
+      fluxSquare->data.F32[j] += PS_SQR(flux->data.F32[i]);
+      fluxWeight->data.F32[j] += weight->data.F32[i];
+      pixelCount->data.F32[j] += 1.0;
+    }
+  }
+
+  for (int j = 0; j < radialBinsLower->n; j++) {
+    fluxValues->data.F32[j] /= pixelCount->data.F32[j];
+    fluxSquare->data.F32[j] /= pixelCount->data.F32[j];
+    fluxSquare->data.F32[j] -= PS_SQR(fluxValues->data.F32[j]);
+  }
+  
+  source->extpars->annuli = pmSourceAnnuliAlloc ();
+  source->extpars->annuli->flux    = fluxValues;
+  source->extpars->annuli->fluxErr = fluxWeight;
+  source->extpars->annuli->fluxVar = fluxSquare;
+
+  return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotApResid.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotApResid.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotApResid.c	(revision 22147)
@@ -0,0 +1,349 @@
+# include "psphotInternal.h"
+
+# define SKIPSTAR(MSG) { psTrace ("psphot", 3, "invalid : %s", MSG); continue; }
+// measure the aperture residual statistics and 2D variations
+
+bool psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf)
+{
+    int Nfail = 0;
+    int Nskip = 0;
+    int Npsf;
+    bool status;
+    pmModel *model;
+    pmSource *source;
+
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+    // XXX drop this? PS_ASSERT_PTR_NON_NULL(psf->ApTrend, false);
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType mark = psMetadataLookupU8(&status, recipe, "MASK.MARK"); // Mask value for bad pixels
+    assert (mark);
+
+    // S/N limit to perform full non-linear fits
+    float MAX_AP_OFFSET = psMetadataLookupF32 (&status, recipe, "MAX_AP_OFFSET");
+
+    // this is the smallest radius allowed: need to at least extend growth curve down to this...
+    float PSF_FIT_PAD   = psMetadataLookupF32 (&status, recipe, "PSF_FIT_PADDING");
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+
+    // XXX is this still needed?  the pmTrend2D stuff should be auto-adjusting...
+    int APTREND_NSTAR_MIN = psMetadataLookupS32(&status, recipe, "APTREND.NSTAR.MIN");
+    assert (status);
+
+    // maximum order for aperture correction
+    int APTREND_ORDER_MAX = psMetadataLookupS32(&status, recipe, "APTREND.ORDER.MAX");
+    assert (status);
+
+    if (APTREND_ORDER_MAX < 1) {
+        psError(PSPHOT_ERR_CONFIG, true, "APTREND.ORDER.MAX must be 1 or more");
+        return false;
+    }
+
+    pmSourcePhotometryMode photMode = 0;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+
+    // set limits on the aperture magnitudes
+    pmSourceMagnitudesInit (recipe);
+
+    // measure the aperture loss as a function of radius for PSF
+    float REF_RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_REF_RADIUS");
+    psf->growth = pmGrowthCurveAlloc (PSF_FIT_PAD, 100.0, REF_RADIUS);
+
+    if (!pmGrowthCurveGenerate (readout, psf, IGNORE_GROWTH, maskVal, mark)) {
+        psError(PSPHOT_ERR_APERTURE, false, "Fitting aperture corrections");
+        psFree(psf->growth); psf->growth = NULL;
+        return false;
+    }
+
+    psVector *mask    = psVectorAllocEmpty (300, PS_TYPE_U8);
+    psVector *mag     = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *xPos    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *yPos    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *apResid = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *dMag    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    Npsf = 0;
+
+    // select all good PM_SOURCE_TYPE_STAR entries
+    for (int i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        model = source->modelPSF;
+
+        if (source->type != PM_SOURCE_TYPE_STAR) SKIPSTAR ("NOT STAR");
+        if (source->mode &  PM_SOURCE_MODE_SATSTAR) SKIPSTAR ("SATSTAR");
+        if (source->mode &  PM_SOURCE_MODE_BLEND) SKIPSTAR ("BLEND");
+        if (source->mode &  PM_SOURCE_MODE_FAIL) SKIPSTAR ("FAIL STAR");
+        if (source->mode &  PM_SOURCE_MODE_POOR) SKIPSTAR ("POOR STAR");
+
+        // get growth-corrected, apTrend-uncorrected magnitudes in scaled apertures
+        // will fail if below S/N threshold or model is missing
+        if (!pmSourceMagnitudes (source, psf, photMode, maskVal, mark)) {
+            Nskip ++;
+	    psTrace ("psphot", 3, "skip : bad source mag");
+            continue;
+        }
+
+        if (!isfinite(source->apMag) || !isfinite(source->psfMag)) {
+            Nfail ++;
+	    psTrace ("psphot", 3, "fail : nan mags : %f %f", source->apMag, source->psfMag);
+            continue;
+        }
+
+	// aperture residual for this source
+	float dap = source->apMag - source->psfMag;
+
+        // sanity clipping : if the model is very discrepant, put your expectation in the recipe
+        if ((MAX_AP_OFFSET > 0) && (fabs(dap) > MAX_AP_OFFSET)) {
+            Nfail ++;
+	    psTrace ("psphot", 3, "fail : bad dap %f %f", dap, MAX_AP_OFFSET);
+            continue;
+        }
+
+	mag->data.F32[Npsf]     = source->psfMag;
+        apResid->data.F32[Npsf] = dap;
+        xPos->data.F32[Npsf]    = model->params->data.F32[PM_PAR_XPOS];
+        yPos->data.F32[Npsf]    = model->params->data.F32[PM_PAR_YPOS];
+
+        mask->data.U8[Npsf] = 0;
+
+        dMag->data.F32[Npsf] = model->dparams->data.F32[PM_PAR_I0] / model->params->data.F32[PM_PAR_I0];
+
+        psVectorExtend (mag,     100, 1);
+        psVectorExtend (mask,    100, 1);
+        psVectorExtend (xPos,    100, 1);
+        psVectorExtend (yPos,    100, 1);
+        psVectorExtend (dMag,    100, 1);
+        psVectorExtend (apResid, 100, 1);
+        Npsf ++;
+    }
+    
+    psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "measure aperture residuals for %d objects (%d skipped, %d failed, %ld invalid)\n",
+              Npsf, Nskip, Nfail, sources->n - Npsf - Nskip - Nfail);
+
+    // XXX choose a better value here?
+    if (Npsf < APTREND_NSTAR_MIN) {
+        psError(PSPHOT_ERR_APERTURE, true, "Only %d valid aperture residual sources (need %d), giving up",
+                Npsf, APTREND_NSTAR_MIN);
+        return false;
+    }
+
+    // XXX deprecating the old code which allowed the ApResid to be fitted as a function of flux and r^2/flux
+    // XXX is this asymmetric clipping still needed?  this analysis should come after neighbors are subtracted...
+    // 3hi/1lo sigma clipping on the rflux vs metric fit
+    // systematic error information
+    float errorScale = 0.0;
+    float errorFloor = 0.0;
+
+    float errorFloorMin = FLT_MAX;
+    int entryMin = -1;
+
+    // Fit out the dap vs mag trend, iterate over spatial scale until error Floor increases.
+    // Stop if Npsf / (Nx * Ny) < 3
+    for (int i = 1; i <= APTREND_ORDER_MAX; i++) {
+
+	if (!psphotApResidTrend (readout, psf, Npsf, i, &errorScale, &errorFloor, mask, xPos, yPos, apResid, dMag)) {
+	    break;
+	}
+
+	// store the resulting errorFloor values and the scales, redo the best
+	if (errorFloor < errorFloorMin) {
+	    errorFloorMin = errorFloor;
+	    entryMin = i;
+	}
+    }
+    if (entryMin == -1) {
+	psAbort ("failed on ApResid Trend");
+    }
+
+    // XXX catch error condition 
+    psphotApResidTrend (readout, psf, Npsf, entryMin, &errorScale, &errorFloor, mask, xPos, yPos, apResid, dMag);
+
+    // construct the fitted values and the residuals
+    psVector *apResidFit = pmTrend2DEvalVector (psf->ApTrend, xPos, yPos);
+    psVector *apResidRes = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) apResidFit);
+    psVector *dMagSys = (psVector *) psBinaryOp (NULL, (void *) dMag, "*", (void *) psScalarAlloc(errorScale, PS_TYPE_F32));
+
+    if (psTraceGetLevel("psphot") >= 5) {
+	FILE *dumpFile = fopen ("apresid.dat", "w");
+	for (int i = 0; i < xPos->n; i++) {
+	    fprintf (dumpFile, "%f %f  %f %f %f  %f %f  %x\n", 
+		     xPos->data.F32[i], yPos->data.F32[i], 
+		     mag->data.F32[i], dMag->data.F32[i], dMagSys->data.F32[i],
+		     apResid->data.F32[i], apResidRes->data.F32[i],
+		     mask->data.U8[i]);
+	}
+	fclose (dumpFile);
+    }
+
+    // apply ApTrend results
+    float xc = 0.5*readout->image->numCols + readout->image->col0 + 0.5;
+    float yc = 0.5*readout->image->numRows + readout->image->row0 + 0.5;
+
+    psf->ApResid  = pmTrend2DEval (psf->ApTrend, xc, yc); // ap-fit at chip center
+    psf->dApResid = errorFloor;
+    psf->nApResid = Npsf;
+
+    // save results for later output
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   0.0);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYSAT",   PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   0.0);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->growth->apLoss);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "ap residual scatter", psf->nApResid);
+
+    // psLogMsg ("psphot.apresid", PS_LOG_INFO, "measure full-frame aperture residuals for %d of %d objects: %f sec\n", Nkeep, Npsf, psTimerMark ("psphot"));
+    psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "aperture residual: %f +/- %f\n", psf->ApResid, psf->dApResid);
+
+    psFree (mag);
+    psFree (mask);
+    psFree (xPos);
+    psFree (yPos);
+
+    psFree (apResid);
+    psFree (apResidFit);
+    psFree (apResidRes);
+
+    psFree (dMagSys);
+    psFree (dMag);
+
+    return true;
+}
+
+/*
+  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
+  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
+  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
+*/
+
+bool psphotMagErrorScale (float *errorScale, float *errorFloor, psVector *dMag, psVector *dap, psVector *mask, int nGroup) {
+
+    psStats *statsS = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psStats *statsM = psStatsAlloc (PS_STAT_SAMPLE_MEAN);
+
+    // measure the trend in bins with 10 values each; if < 10 total, use them all
+    int nBin = PS_MAX (dMag->n / nGroup, 1);
+
+    // output vectors for ApResid trend 
+    psVector *dSo = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVector *dMo = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVector *dRo = psVectorAlloc (nBin, PS_TYPE_F32);
+
+    // use dMag to group the dMag and dap vectors
+    psVector *index = psVectorSortIndex (NULL, dMag);
+
+    // subset vectors for dMag and dap values within the given range
+    psVector *dMSubset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
+    psVector *dASubset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
+    psVector *mkSubset = psVectorAllocEmpty (nGroup, PS_TYPE_U8);
+
+    int n = 0;
+    for (int i = 0; i < dMo->n; i++) {
+	int j;
+	for (j = 0; (j < nGroup) && (n < dMag->n); j++, n++) {
+	    int N = index->data.U32[n];
+	    dMSubset->data.F32[j] = dMag->data.F32[N];
+	    dASubset->data.F32[j] = dap->data.F32[N];
+	    mkSubset->data.U8[j]  = mask->data.U8[N];
+	}
+	dMSubset->n = j;
+	dASubset->n = j;
+	mkSubset->n = j;
+
+	psStatsInit (statsS);
+	psStatsInit (statsM);
+
+	psVectorStats (statsS, dASubset, NULL, mkSubset, 0xff);
+	psVectorStats (statsM, dMSubset, NULL, mkSubset, 0xff);
+
+	dSo->data.F32[i] = statsS->robustStdev;
+	dMo->data.F32[i] = statsM->sampleMean;
+
+	dRo->data.F32[i] = statsS->robustStdev / statsM->sampleMean;
+    }
+    psFree (dMSubset);
+    psFree (dASubset);
+    psFree (mkSubset);
+    
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, dRo, NULL, NULL, 0);
+
+    *errorScale = stats->sampleMedian;
+    for (int i = 0; i < dSo->n; i++) {
+	*errorFloor = dSo->data.F32[i];
+	if (isfinite(*errorFloor)) break;
+    }
+
+    psFree (stats);
+    psFree (index);
+
+    psFree (dRo);
+    psFree (dMo);
+    psFree (dSo);
+    
+    psFree (statsS);
+    psFree (statsM);
+
+    return true;
+}
+
+bool psphotApResidTrend (pmReadout *readout, pmPSF *psf, int Npsf, int scale, float *errorScale, float *errorFloor, psVector *mask, psVector *xPos, psVector *yPos, psVector *apResid, psVector *dMag) {
+
+    int Nx, Ny;
+	
+    if (readout->image->numCols > readout->image->numRows) {
+	Nx = scale;
+	Ny = PS_MAX (1, Nx * (readout->image->numRows / (float) readout->image->numCols));
+    } else {
+	Ny = scale;
+	Nx = PS_MAX (1, Ny * (readout->image->numCols / (float) readout->image->numRows));
+    }
+
+    if (Npsf < 3*Nx*Ny) {
+	return false;
+    }
+
+    // the mask marks the values not used to calculate the ApTrend
+    psVectorInit (mask, 0);
+
+    // XXX stats structure for use by ApTrend : make parameters user setable
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    stats->min = 2.0;
+    stats->max = 3.0;
+
+    // measure Trend2D for the current spatial scale
+    psFree (psf->ApTrend);
+    psf->ApTrend = pmTrend2DAlloc (PM_TREND_MAP, readout->image, Nx, Ny, stats);
+
+    // XXX test for errors here
+    pmTrend2DFit (psf->ApTrend, mask, 0xff, xPos, yPos, apResid, dMag);
+    
+    // construct the fitted values and the residuals
+    psVector *apResidFit = pmTrend2DEvalVector (psf->ApTrend, xPos, yPos);
+    psVector *apResidRes = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) apResidFit);
+
+    // measure systematic errorFloor & systematic / photon scale factor
+    // XXX this is a bit arbitrary, but it forces ~3 stars from the bright bin per spatial bin
+    int nGroup = PS_MAX (3*Nx*Ny, 10);
+    psphotMagErrorScale (errorScale, errorFloor, dMag, apResidRes, mask, nGroup);
+
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "result of %d x %d grid (%d stars per bin)\n", Nx, Ny, nGroup);
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "systematic error / photon error: %f\n", *errorScale);
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "systematic scatter floor: %f\n", *errorFloor);
+
+    psFree (stats);
+    psFree (apResidFit);
+    psFree (apResidRes);
+
+    return true;
+}
+    
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotArguments.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotArguments.c	(revision 22147)
@@ -0,0 +1,122 @@
+# include "psphotStandAlone.h"
+
+pmConfig *psphotArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-version"))) {
+        psString version;
+        version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+        psError(PSPHOT_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // run the test model (requires X,Y coordinate)
+    if ((N = psArgumentGet (argc, argv, "-modeltest"))) {
+        psMetadataAddBool (options, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_Y", 0, "", atof(argv[N+2]));
+
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+
+        // specify the modeltest model
+        if ((N = psArgumentGet (argc, argv, "-model"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODEL", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+
+        // specify the test fit mode
+        if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+        if ((N = psArgumentGet (argc, argv, "-fitset"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+    }
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // fitmode : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "FITMODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+        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"))) {
+        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, "WEIGHT", "-weight", "-weightlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf", "-psflist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC",    "-src",    "-srclist");
+
+    // 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");
+        return NULL;
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+        return NULL;
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotBasicDeblend.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotBasicDeblend.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotBasicDeblend.c	(revision 22147)
@@ -0,0 +1,129 @@
+# include "psphotInternal.h"
+
+bool psphotBasicDeblend (psArray *sources, psMetadata *recipe) {
+
+    int N;
+    bool status;
+    float threshold;
+    pmSource *source, *testSource;
+
+    int Nblend = 0;
+
+    psTimerStart ("psphot");
+
+    float FRACTION = psMetadataLookupF32 (&status, recipe, "DEBLEND_PEAK_FRACTION");
+    if (!status) FRACTION = 0.25;
+
+    float NSIGMA   = psMetadataLookupF32 (&status, recipe, "DEBLEND_SKY_NSIGMA");
+    if (!status) NSIGMA = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // source analysis is done in peak order (brightest first)
+    // we use an index for this so the spatial sorting is kept
+    psVector *SN = psVectorAlloc (sources->n, PS_DATA_F32);
+    for (int i = 0; i < SN->n; i++) {
+        source = sources->data[i];
+        SN->data.F32[i] = source->peak->SN;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+        N = index->data.U32[i];
+        source = sources->data[N];
+
+        if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+
+        // temporary array for overlapping objects we find
+        psArray *overlap = psArrayAllocEmpty (100);
+
+        // search backwards for overlapping sources
+        for (int j = N - 1; j >= 0; j--) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) break;
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        // search forwards for overlapping sources
+        for (int j = N + 1; j < sources->n; j++) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        if (overlap->n == 0) {
+            psFree (overlap);
+            continue;
+        }
+
+        // this source has overlapping neighbors, check for actual blends
+        // generate source contour (1/4 peak counts)
+        // set the threshold based on user inputs
+
+        // threshold is fraction of the source peak flux
+        // image is background subtracted; source->moments->Sky should always be 0.0
+        threshold = FRACTION * source->peak->SN;
+        // threshold is no less than NSIGMA
+        threshold = PS_MAX (threshold, NSIGMA);
+
+        // generate a basic contour (set of x,y coordinates at-or-below flux level)
+        psArray *contour = pmSourceContour (source->pixels, source->peak->x, source->peak->y, threshold);
+        if (contour == NULL) {
+            psFree (overlap);
+            continue;
+        }
+
+        // the source contour consists of two vectors, xv and yv.  the contour is
+        // a series of coordinate pairs, (xv[i],yv[i]) & (xv[i+1],yv[i+1]).  both
+        // coordinate pairs have the same yv[] value, with xv[i] corresponding to
+        // the left boundary and xv[i+1] corresponding to the right boundary
+
+	// a blend can only be associated with one primary source
+
+        psVector *xv = contour->data[0];
+        psVector *yv = contour->data[1];
+        for (int k = 0; k < overlap->n; k++) {
+            testSource = overlap->data[k];
+	    if (testSource->mode & PM_SOURCE_MODE_BLEND) continue;
+            if (testSource->peak->value > source->peak->value) continue;
+            for (int j = 0; j < xv->n; j+=2) {
+                if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+                if (xv->data.F32[j+0] > testSource->peak->x) break;
+                if (xv->data.F32[j+1] < testSource->peak->x) break;
+
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+
+                // add this to the list of source->blends
+                if (source->blends == NULL) {
+                    source->blends = psArrayAllocEmpty (16);
+                }
+                psArrayAdd (source->blends, 16, testSource);
+
+                Nblend ++;
+                j = xv->n;
+            }
+        }
+        psFree (overlap);
+        psFree (contour);
+    }
+    psLogMsg ("psphot.deblend", PS_LOG_INFO, "identified %d blended objects: %f sec\n", Nblend, psTimerMark ("psphot"));
+
+    psFree (SN);
+    psFree (index);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotBlendFit.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotBlendFit.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotBlendFit.c	(revision 22147)
@@ -0,0 +1,107 @@
+# include "psphotInternal.h"
+
+// XXX I don't like this name
+bool psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    int Nfit = 0;
+    int Npsf = 0;
+    int Next = 0;
+    int Nfail = 0;
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // S/N limit to perform full non-linear fits
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, recipe, "FULL_FIT_SN_LIM");
+
+    psphotInitLimitsPSF (recipe, readout);
+    psphotInitLimitsEXT (recipe);
+    psphotInitRadiusPSF (recipe, psf->type);
+
+    psphotFitInit ();
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    for (int i = 0; i < sources->n; i++) {
+        // if (i%100 == 0) psphotFitSummary ();
+
+        pmSource *source = sources->data[i];
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // skip DBL second sources (ie, added by psphotFitBlob)
+        if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+
+        // limit selection to some SN limit
+        // XXX this should use peak?
+        if (source->moments == NULL) continue;
+        if (source->moments->SN < FIT_SN_LIM) continue;
+
+        // XXX this should use peak?
+        if (source->moments->x < AnalysisRegion.x0) continue;
+        if (source->moments->y < AnalysisRegion.y0) continue;
+        if (source->moments->x > AnalysisRegion.x1) continue;
+        if (source->moments->y > AnalysisRegion.y1) continue;
+
+        // if model is NULL, we don't have a starting guess
+        if (source->modelPSF == NULL) continue;
+
+        // skip sources which are insignificant flux?
+        if (source->modelPSF->params->data.F32[1] < 0.1) {
+            psTrace ("psphot", 5, "skipping near-zero source: %f, %f : %f\n",
+                     source->modelPSF->params->data.F32[1],
+                     source->modelPSF->params->data.F32[2],
+                     source->modelPSF->params->data.F32[3]);
+            continue;
+        }
+
+        // replace object in image
+        if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+        Nfit ++;
+
+        // try fitting PSFs, then try extended sources
+        // these functions subtract the resulting fitted source (XXX and update the modelFlux?)
+	// XXX re-consider conditions under which the source has EXT fit:
+	// I should try EXT if the source size measurement says it is large
+        if (psphotFitBlend (readout, source, psf, maskVal)) {
+            psTrace ("psphot", 5, "source at %7.1f, %7.1f is psf", source->moments->x, source->moments->y);
+            Npsf ++;
+            continue;
+        }
+        if (psphotFitBlob (readout, source, sources, psf, maskVal)) {
+            psTrace ("psphot", 5, "source at %7.1f, %7.1f is ext", source->moments->x, source->moments->y);
+            Next ++;
+            continue;
+        }
+
+        psTrace ("psphot", 5, "source at %7.1f, %7.1f failed", source->moments->x, source->moments->y);
+        Nfail ++;
+
+        // re-subtract the object, leave local sky
+        pmSourceCacheModel (source, maskVal);
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+
+    if (psTraceGetLevel("psphot") >= 6) {
+      psphotSaveImage (NULL, readout->image,  "image.v2.fits");
+    }
+
+    psLogMsg ("psphot.psphotBlendFit", PS_LOG_INFO, "fit models: %f sec for %d objects (%d psf, %d ext, %d failed, %ld skipped)\n", psTimerMark ("psphot"), Nfit, Npsf, Next, Nfail, sources->n - Nfit);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotCheckStarDistribution.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotCheckStarDistribution.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotCheckStarDistribution.c	(revision 22147)
@@ -0,0 +1,104 @@
+# include "psphotInternal.h"
+
+// examine the x,y distribution of the psfstars and extend the selection if needed
+// desired region and coverage are specified by pmPSFOptions
+// even if stars->n is at the limit, we will add more sources to fill out the area
+
+bool psphotCheckStarDistribution (psArray *stars, psArray *sources, pmPSFOptions *options) {
+
+    // count the number of PSFSTAR sources in each cell.  for polynomial representations, we
+    // use an image (Norder + 1) x (Norder + 1)
+    int Nx = options->psfTrendNx;
+    int Ny = options->psfTrendNy;
+    if (options->psfTrendMode != PM_TREND_MAP) {
+	Nx ++;
+	Ny ++;
+    }
+
+    // set up and image and an image binning structure to cover the field
+    psImage *nCell = psImageAlloc (Nx, Ny, PS_TYPE_S32);
+    psImageInit (nCell, 0);
+
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXruff = Nx;
+    binning->nYruff = Ny;
+    binning->nXfine = options->psfFieldNx;
+    binning->nYfine = options->psfFieldNy;
+
+    psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkipByOffset (binning, options->psfFieldXo, options->psfFieldYo);
+
+    // where are the PSF stars located?
+    for (int i = 0; i < stars->n; i++) {
+	
+	pmSource *source = stars->data[i];
+        if (source->peak == NULL) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+	int binX = psImageBinningGetRuffX (binning, source->peak->xf);
+	int binY = psImageBinningGetRuffY (binning, source->peak->yf);
+
+	assert (binX >= 0);
+	assert (binY >= 0);
+	assert (binX <  nCell->numCols);
+	assert (binY <  nCell->numRows);
+
+	nCell->data.S32[binY][binX] ++;
+    }
+	
+    // do any cells have too few PSFSTAR sources?
+    // we use 3 as a minimum (slightly arbitrary...)
+    for (int iy = 0; iy < nCell->numRows; iy++) { 
+	for (int ix = 0; ix < nCell->numCols; ix++) { 
+	    if (nCell->data.S32[iy][ix] < 3) {
+		int nNew = psphotSupplementStars (stars, sources, binning, ix, iy);
+		if (nNew) {
+		    psLogMsg ("pmObjects", 3, "added %d fainter PSF candidates to %d,%d\n", nNew, ix, iy);
+		} else {
+		    psLogMsg ("pmObjects", 3, "tried to add to %d,%d, but no more sources are available\n", ix, iy);		    
+		}
+	    }
+	}
+    }
+
+    psFree (nCell);
+    psFree (binning);
+    return true;
+}
+
+// select more possible PSF stars in the specified cell
+// sources should be sorted by SN before calling this function
+int psphotSupplementStars (psArray *stars, psArray *sources, psImageBinning *binning, int ix, int iy) {
+
+    int nNew = 0;
+
+    float Xs = psImageBinningGetFineX (binning, ix);
+    float Xe = psImageBinningGetFineX (binning, ix + 1);
+
+    float Ys = psImageBinningGetFineY (binning, iy);
+    float Ye = psImageBinningGetFineY (binning, iy + 1);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	// add sources which are marked as stars, in S/N order until...
+	pmSource *source = sources->data[i];
+        if (source->peak == NULL) continue;
+        if (source->moments == NULL) continue;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+
+	float x = source->peak->xf;
+	float y = source->peak->yf;
+
+	if (x < Xs) continue;
+	if (y < Ys) continue;
+	if (x > Xe) continue;
+	if (y > Ye) continue;
+
+	source->mode |= PM_SOURCE_MODE_PSFSTAR;
+	psArrayAdd (stars, 200, source);
+
+	nNew ++;
+    }
+    return nNew;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotChoosePSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotChoosePSF.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotChoosePSF.c	(revision 22147)
@@ -0,0 +1,466 @@
+# include "psphotInternal.h"
+
+// try PSF models and select best option
+pmPSF *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType mark = psMetadataLookupU8(&status, recipe, "MASK.MARK"); // Mask value for bad pixels
+    assert (mark);
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // structure to store user options defining the psf
+    pmPSFOptions *options = pmPSFOptionsAlloc ();
+
+    // load user options from the recipe. no need to check existence -- they are 
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    assert (status);
+
+    float PSF_MIN_DS = psMetadataLookupF32 (&status, recipe, "PSF_MIN_DS");
+    assert (status);
+
+    // supply the measured sky variance for optional constant errors (non-poissonian)
+    float SKY_SIG = psMetadataLookupF32 (&status, recipe, "SKY_SIG");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsPhotLMM = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PHOT.LMM");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsPhotLin = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PHOT.LIN");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsParams = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PARAMS");
+    assert (status);
+
+    // how to model the PSF variations across the field
+    options->psfTrendMode = pmTrend2DModeFromString (psMetadataLookupStr (&status, recipe, "PSF.TREND.MODE"));
+    assert (status);
+    
+    options->psfTrendNx = psMetadataLookupS32 (&status, recipe, "PSF.TREND.NX");
+    assert (status);
+
+    options->psfTrendNy = psMetadataLookupS32 (&status, recipe, "PSF.TREND.NY");
+    assert (status);
+
+    // get the fixed PSF fit radius
+    // XXX check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    options->radius = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    assert (status);
+
+    options->stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+
+    // dimensions of the field for which the PSF is defined
+    options->psfFieldNx = readout->image->numCols;
+    options->psfFieldNy = readout->image->numRows;
+    options->psfFieldXo = readout->image->col0;
+    options->psfFieldYo = readout->image->row0;
+
+    pmSourceFitModelInit (15, 0.01, PS_SQR(SKY_SIG), options->poissonErrorsPhotLMM);
+
+    psArray *stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+            // keep NSTARS PSF stars, unmark the rest
+            if (stars->n < NSTARS) {
+                psArrayAdd (stars, 200, source);
+            } else {
+                source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+            }
+        }
+    }
+    // check that the identified psf stars sufficiently cover the region; if not, extend the
+    // limits somewhat
+    psphotCheckStarDistribution (stars, sources, options);
+
+    psLogMsg ("psphot.pspsf", PS_LOG_DETAIL, "selected candidate %ld PSF objects\n", stars->n);
+
+    // get the list pointers for the PSF_MODEL entries
+    psArray *modelNames = NULL;
+    psMetadataItem *item = psMetadataLookup (recipe, "PSF_MODEL");
+    if (item == NULL) psAbort("missing PSF_MODEL selection");
+
+    if (item->type == PS_DATA_STRING) {
+        modelNames = psArrayAlloc(1);
+        modelNames->data[0] = psStringCopy (item->data.V);
+    } else {
+        if (item->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+	modelNames = psListToArray (item->data.list);
+    }
+
+    // generate a psf model using the first selection
+    if (stars->n == 0) {
+	// XXX optionally give up here
+	psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+	psErrorStackPrint (stderr, "Using guess PSF model");
+	psErrorClear ();
+	
+	// unset the PSFSTAR flags (none are used):
+	for (int i = 0; i < sources->n; i++) {
+	    pmSource *source = sources->data[i];
+	    source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+	}
+
+	// XXX set sxx, etc from FWHM in recipe
+	pmPSF *psf = pmPSFBuildSimple (modelNames->data[0], 1.0, 1.0, 0.0, 1.0);
+	psf->fieldNx = readout->image->numCols;
+	psf->fieldNy = readout->image->numRows;
+	psFree (modelNames);
+
+	bool status = true;
+	status &= psphotMakeFluxScale (readout->image, recipe, psf);
+	status &= psphotPSFstats (readout, recipe, psf);
+	if (!status) {
+	    psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+	    psFree (psf);
+	    return NULL;
+	}
+
+	// XXX set DSX_MEAN, etc?
+        return psf;
+    }
+
+    // set up an array to store the results
+    psArray *models = psArrayAlloc (modelNames->n);
+
+    // try each model option listed in config
+    for (int i = 0; i < modelNames->n; i++) {
+        char *modelName = modelNames->data[i];
+        models->data[i] = pmPSFtryModel (stars, modelName, options, maskVal, mark);
+    }
+
+    // select the best of the models
+    // here we are using the clippedStdev on the metric as the indicator
+    int bestN = -1;
+    float bestM = 0.0;
+    for (int i = 0; i < models->n; i++) {
+        pmPSFtry *try = models->data[i];
+        if (try == NULL) {
+            psTrace ("psphot", 3, "PSF model %d is NULL", i);
+            continue;
+        }
+        float M = try->psf->dApResid;
+        if (bestN < 0 || M < bestM) {
+            bestM = M;
+            bestN = i;
+        }
+    }
+
+    // use the best model:
+    if (bestN < 0) {
+        psFree (models);
+	psFree (stars);
+
+	// XXX optionally give up here
+	// generate a psf model using the first selection
+	psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+	psErrorStackPrint (stderr, "Using guess PSF model");
+	psErrorClear ();
+	
+	// unset the PSFSTAR flags (none are used):
+	for (int i = 0; i < sources->n; i++) {
+	    pmSource *source = sources->data[i];
+	    source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+	}
+
+	// XXX set sxx, etc from FWHM in recipe
+	pmPSF *psf = pmPSFBuildSimple (modelNames->data[0], 1.0, 1.0, 0.0, 1.0);
+	psf->fieldNx = readout->image->numCols;
+	psf->fieldNy = readout->image->numRows;
+	psFree (modelNames);
+
+	bool status = true;
+	status &= psphotMakeFluxScale (readout->image, recipe, psf);
+	status &= psphotPSFstats (readout, recipe, psf);
+	if (!status) {
+	    psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+	    psFree (psf);
+	    return NULL;
+	}
+
+	// XXX set DSX_MEAN, etc?
+        return psf;
+    }
+
+    psFree (modelNames);
+    psFree (stars);
+
+    pmPSFtry *try = models->data[bestN];
+
+    // measure and save the median value of dparams[PM_PAR_SXX],dparams[PM_PAR_SYY]
+    // these are used by psphotEvalPSF to identify extended sources
+    // XXX is this still needed?
+    psVector *Sx = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *Sy = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSx = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSy = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSN = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    for (int i = 0; i < try->sources->n; i++) {
+        // masked for: bad model fit, outlier in parameters
+        if (try->mask->data.U8[i] & PSFTRY_MASK_ALL)
+            continue;
+
+        pmSource *source = try->sources->data[i];
+        Sx->data.F32[Sx->n] = source->modelPSF->params->data.F32[PM_PAR_SXX];
+        Sy->data.F32[Sy->n] = source->modelPSF->params->data.F32[PM_PAR_SYY];
+        dSN->data.F32[dSN->n] = source->modelPSF->dparams->data.F32[PM_PAR_I0] / source->modelPSF->params->data.F32[PM_PAR_I0];
+        dSx->data.F32[dSx->n] = source->modelPSF->dparams->data.F32[PM_PAR_SXX];
+        dSy->data.F32[dSy->n] = source->modelPSF->dparams->data.F32[PM_PAR_SYY];
+        Sx->n ++;
+        Sy->n ++;
+        dSN->n ++;
+        dSx->n ++;
+        dSy->n ++;
+    }
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    psVectorStats (stats, dSx, NULL, NULL, 0);
+    float dSxo = stats->sampleMean;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSxo: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSx->n);
+
+    psVectorStats (stats, dSy, NULL, NULL, 0);
+    float dSyo = stats->sampleMean;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSyo: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSy->n);
+
+    for (int i = 0; i < dSN->n; i++) {
+        dSx->data.F32[i] = (dSx->data.F32[i] - dSxo) / PS_MAX (PSF_MIN_DS, Sx->data.F32[i]*dSN->data.F32[i]);
+        dSy->data.F32[i] = (dSy->data.F32[i] - dSyo) / PS_MAX (PSF_MIN_DS, Sy->data.F32[i]*dSN->data.F32[i]);
+    }
+
+    psVectorStats (stats, dSx, NULL, NULL, 0);
+    float nSx = stats->sampleStdev;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "ndSx: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSx->n);
+    psVectorStats (stats, dSy, NULL, NULL, 0);
+    float nSy = stats->sampleStdev;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "ndSy: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSy->n);
+
+    psFree (Sx);
+    psFree (Sy);
+    psFree (dSx);
+    psFree (dSy);
+    psFree (dSN);
+    psFree (stats);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSX_MEAN", PS_META_REPLACE, "mean offset of dSXX", dSxo);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSY_MEAN", PS_META_REPLACE, "mean offset of dSYY", dSyo);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSX_STDV", PS_META_REPLACE, "stdev of mean-corrected dSXX", nSx);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSY_STDV", PS_META_REPLACE, "stdev of mean-corrected dSYY", nSy);
+
+    // unset the PSFSTAR flag for stars not used for PSF model
+    for (int i = 0; i < try->sources->n; i++) {
+        pmSource *source = try->sources->data[i];
+        if (try->mask->data.U8[i]) {
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+    }
+
+    // build a PSF residual image
+    if (!psphotMakeResiduals (try->sources, recipe, try->psf, maskVal)) {
+        psError(PSPHOT_ERR_PSF, false, "Unable to construct residual table for PSF");
+        psFree (models);
+        return NULL;
+    }
+
+    // build a PSF residual image
+    if (!psphotMakeFluxScale (readout->image, recipe, try->psf)) {
+        psError(PSPHOT_ERR_PSF, false, "Unable to construct flux scale for PSF");
+        psFree (models);
+        return NULL;
+    }
+
+    // XXX test dump of psf star data and psf-subtracted image
+    if (psTraceGetLevel("psphot.psfstars") > 5) {
+        psphotSaveImage (NULL, readout->image,  "rawstars.fits");
+
+        for (int i = 0; i < try->sources->n; i++) {
+            // masked for: bad model fit, outlier in parameters
+            if (try->mask->data.U8[i] & PSFTRY_MASK_ALL)
+                continue;
+
+            pmSource *source = try->sources->data[i];
+            float x = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+            float y = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+
+            // set the mask and subtract the PSF model
+            // XXX should we be using maskObj? should we be unsetting the mask?
+            // use pmModelSub because modelFlux has not been generated
+            assert (source->maskObj);
+            psImageKeepCircle (source->maskObj, x, y, options->radius, "OR", PM_MASK_MARK);
+            pmModelSub (source->pixels, source->maskObj, source->modelPSF, PM_MODEL_OP_FULL, maskVal);
+            psImageKeepCircle (source->maskObj, x, y, options->radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+        }
+
+        FILE *f = fopen ("shapes.dat", "w");
+        for (int i = 0; i < try->sources->n; i++) {
+            psF32 inPar[10];  // must be psF32 to pmPSF_FitToModel
+
+            // masked for: bad model fit, outlier in parameters
+            if (try->mask->data.U8[i] & PSFTRY_MASK_ALL) continue;
+
+            pmSource *source = try->sources->data[i];
+            psF32 *outPar = source->modelEXT->params->data.F32;
+
+            psEllipseShape shape;
+
+            shape.sx  = outPar[PM_PAR_SXX] / M_SQRT2;
+            shape.sy  = outPar[PM_PAR_SYY] / M_SQRT2;
+            shape.sxy = outPar[PM_PAR_SXY];
+
+            psEllipsePol pol = pmPSF_ModelToFit (outPar);
+            inPar[PM_PAR_E0] = pol.e0;
+            inPar[PM_PAR_E1] = pol.e1;
+            inPar[PM_PAR_E2] = pol.e2;
+            pmPSF_FitToModel (inPar, 0.1);
+
+            psEllipseAxes axes1 = psEllipseShapeToAxes (shape, 20.0);
+            psEllipseAxes axes2;
+            (void)psEllipsePolToAxes(pol, 0.1, &axes2);
+            psEllipsePol pol2 = psEllipseAxesToPol (axes1);
+
+            fprintf (f, "%3d  %7.2f %7.2f  %7.4f %7.4f %7.4f  --  %7.4f %7.4f %7.4f  :  %7.4f %7.4f %7.4f  --  %7.4f %7.4f %7.4f : %7.4f %7.4f %6.1f : %7.4f %7.4f %6.1f\n",
+                     i, outPar[PM_PAR_XPOS], outPar[PM_PAR_YPOS],
+                     outPar[PM_PAR_SXX], outPar[PM_PAR_SXY], outPar[PM_PAR_SYY],
+                     pol.e0, pol.e1, pol.e2,
+                     pol2.e0, pol2.e1, pol2.e2,
+                     inPar[PM_PAR_SXX], inPar[PM_PAR_SXY], inPar[PM_PAR_SYY],
+                     axes1.major, axes1.minor, axes1.theta*PM_DEG_RAD,
+                     axes2.major, axes2.minor, axes2.theta*PM_DEG_RAD
+                     );
+        }
+        fclose (f);
+
+        psphotSaveImage (NULL, readout->image,  "psfstars.fits");
+        pmSourcesWritePSFs (try->sources, "psfstars.dat");
+        pmSourcesWriteEXTs (try->sources, "extstars.dat", false);
+	// XXX need alternative output function
+        // psMetadata *psfData = pmPSFtoMetadata (NULL, try->psf);
+        // psMetadataConfigWrite (psfData, "psfmodel.dat");
+        psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "wrote out psf-subtracted image, psf data, exiting\n");
+        exit (0);
+    }
+
+    // save only the best model;
+    // XXX we are not saving the fitted sources
+    // XXX do we want to keep them so we may optionally write them out?
+    pmPSF *psf = psMemIncrRefCounter(try->psf);
+    psFree (models);
+
+    if (!psphotPSFstats (readout, recipe, psf)) {
+        psError(PSPHOT_ERR_PSF, false, "cannot measure PSF shape terms");
+        psFree(psf);
+        return NULL;
+    }
+
+    char *modelName = pmModelClassGetName (psf->type);
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "select psf model: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
+
+    psFree (options);
+    return (psf);
+}
+
+// measure average parameters of the PSF model
+bool psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf) {
+
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+
+    psImage *image = readout->image;
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    // construct a normalized PSF model at this coordinate (Io = 1.0)
+    pmModel *modelPSF = pmModelFromPSFforXY (psf, 0.5*image->numCols, 0.5*image->numRows, 1.0);
+    if (modelPSF == NULL) {
+        psError(PSPHOT_ERR_PSF, false, "Failed to estimate PSF model at image centre");
+        return false;
+    }
+
+    // get the model full-width at half-max
+    double FWHM_X = 2*modelPSF->modelRadius (modelPSF->params, 0.5);
+
+    // XXX make sure this is consistent with the re-definition of PM_PAR_SXX
+    shape.sx  = modelPSF->params->data.F32[PM_PAR_SXX];
+    shape.sy  = modelPSF->params->data.F32[PM_PAR_SYY];
+    shape.sxy = modelPSF->params->data.F32[PM_PAR_SXY];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    double FWHM_Y = FWHM_X * (axes.minor / axes.major);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_X",   PS_META_REPLACE, "PSF FWHM Major axis", FWHM_X);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_Y",   PS_META_REPLACE, "PSF FWHM Minor axis", FWHM_Y);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           axes.theta);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", psf->nPSFstars);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", true);
+    psFree (modelPSF);
+
+    return true;
+}
+
+// determine approximate PSF shape parameters based on the moments
+bool psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources) {
+
+    // without the PSF model, we can only rely on the source->moments
+    // as a measure of the FWHM values
+
+    int FWHM_N = 0;
+    double FWHM_X = 0.0;
+    double FWHM_Y = 0.0;
+    double FWHM_T = 0.0;
+
+    psEllipseMoments moments;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!source) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // moments->Sx,Sy are roughly sigma_x,y
+        moments.x2 = PS_SQR(source->moments->Sx);
+        moments.y2 = PS_SQR(source->moments->Sy);
+        moments.xy = source->moments->Sxy;
+
+        // limit axis ratio < 20.0
+        axes = psEllipseMomentsToAxes (moments, 20.0);
+
+        FWHM_X += axes.major * 2.35;
+        FWHM_Y += axes.minor * 2.35;
+        FWHM_T += axes.theta;
+        FWHM_N ++;
+    }
+
+    FWHM_X /= FWHM_N;
+    FWHM_Y /= FWHM_N;
+    FWHM_T /= FWHM_N;
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_X",   PS_META_REPLACE, "PSF FWHM Major axis (from moments)", FWHM_X);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_Y",   PS_META_REPLACE, "PSF FWHM Minor axis (from moments)", FWHM_Y);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           FWHM_T);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", 0);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", false);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotCleanup.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotCleanup.c	(revision 22147)
@@ -0,0 +1,36 @@
+# include "psphotStandAlone.h"
+
+void psphotCleanup (pmConfig *config) {
+
+    psFree (config);
+
+    psTimerStop ();
+    psMemCheckCorruption (stderr, true);
+    pmModelClassCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "psphot");
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psphot");
+    return;
+}
+
+psExit psphotGetExitStatus () {
+
+    psErrorCode err = psErrorCodeLast ();
+    switch (err) {
+      case PS_ERR_NONE:
+	return PS_EXIT_SUCCESS;
+      case PSPHOT_ERR_SYS:
+        return PS_EXIT_SYS_ERROR;
+      case PSPHOT_ERR_CONFIG:
+        return PS_EXIT_CONFIG_ERROR;
+      case PSPHOT_ERR_PROG:
+        return PS_EXIT_PROG_ERROR;
+      case PSPHOT_ERR_DATA:
+        return PS_EXIT_DATA_ERROR;
+      default:
+        return PS_EXIT_UNKNOWN_ERROR;
+    }
+    return PS_EXIT_UNKNOWN_ERROR;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDeblendSatstars.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDeblendSatstars.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDeblendSatstars.c	(revision 22147)
@@ -0,0 +1,159 @@
+# include "psphotInternal.h"
+
+bool psphotDeblendSatstars (psArray *sources, psMetadata *recipe) {
+
+    int N;
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    int Nblend = 0;
+    float SAT_TEST_LEVEL = 50000;
+    float SAT_MIN_RADIUS = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // source analysis is done in peak order (brightest first)
+    // we use an index for this so the spatial sorting is kept
+    psVector *SN = psVectorAlloc (sources->n, PS_DATA_F32);
+    for (int i = 0; i < SN->n; i++) {
+        source = sources->data[i];
+        SN->data.F32[i] = source->peak->SN;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+        N = index->data.U32[i];
+        source = sources->data[N];
+
+        // XXX filter? if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+        if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+        if (source->peak->flux < SAT_TEST_LEVEL) continue;
+
+	// save these for reference below
+	int xPeak = source->peak->x;
+	int yPeak = source->peak->y;
+
+        // generate a basic contour (set of x,y coordinates at-or-below flux level)
+        psArray *contour = pmSourceContour (source->pixels, xPeak, yPeak, SAT_TEST_LEVEL);
+        if (contour == NULL) continue;
+
+	// contour consists of a set of X,Y coords giving the boundary
+	psVector *xVec = contour->data[0];
+	psVector *yVec = contour->data[1];
+
+	// XXX should we filter based on the number of pixels in the contour?
+
+	// find the center of the contour (let's just use mid[x,y])
+	int xMin = xVec->data.F32[0];
+	int xMax = xVec->data.F32[0];
+	int yMin = yVec->data.F32[0];
+	int yMax = yVec->data.F32[0];
+	for (int j = 0; j < xVec->n; j++) {
+	    xMin = PS_MIN (xMin, xVec->data.F32[j]);
+	    xMax = PS_MAX (xMax, xVec->data.F32[j]);
+	    yMin = PS_MIN (yMin, yVec->data.F32[j]);
+	    yMax = PS_MAX (yMax, yVec->data.F32[j]);
+	}	
+	int xCenter = 0.5*(xMin + xMax);
+	int yCenter = 0.5*(yMin + yMax);
+	psFree (contour);
+
+	// reset the peak for this source to the value of the center pixel
+	source->peak->x = xCenter;
+	source->peak->y = yCenter;
+	source->peak->xf = xCenter;
+	source->peak->yf = yCenter;
+	
+	// temporary array for overlapping objects we find
+        psArray *overlap = psArrayAllocEmpty (100);
+
+        // search backwards for overlapping sources
+        for (int j = N - 1; j >= 0; j--) {
+            pmSource *testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) break;
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        // search forwards for overlapping sources
+        for (int j = N + 1; j < sources->n; j++) {
+            pmSource *testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        if (overlap->n == 0) {
+            psFree (overlap);
+            continue;
+        }
+
+	// now find the contour which is at 0.5*SAT_TEST_LEVEL (xPeak, yPeak is valid high pixel)
+        contour = pmSourceContour (source->pixels, xPeak, yPeak, 0.5*SAT_TEST_LEVEL);
+        if (contour == NULL) {
+	    psFree (overlap);
+	    continue; 
+	}
+	
+	// any peaks within this contour should be dropped (mark as blend, drop after loop is done)
+	// also drop any peaks which are too close to this peal
+        psVector *xv = contour->data[0];
+        psVector *yv = contour->data[1];
+        for (int k = 0; k < overlap->n; k++) {
+            pmSource *testSource = overlap->data[k];
+	    float radius = hypot((testSource->peak->x - xCenter), (testSource->peak->y - yCenter));
+	    if (radius < SAT_MIN_RADIUS) {
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+                Nblend ++;
+		continue;
+	    }
+            for (int j = 0; j < xv->n; j+=2) {
+                if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+                if (xv->data.F32[j+0] > testSource->peak->x) break;
+                if (xv->data.F32[j+1] < testSource->peak->x) break;
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+                Nblend ++;
+                j = xv->n; // skip rest of contour
+            }
+        }
+        psFree (overlap);
+        psFree (contour);
+    }
+
+    // drop the sources marked as BLEND
+    for (int i = 0; i < sources->n;) {
+	pmSource *source = sources->data[i];
+
+        if (!(source->mode & PM_SOURCE_MODE_BLEND)) {
+	    i++;
+	    continue;
+	}
+
+	// shuffle the remaining sources forward
+	for (int j = i; j < sources->n - 1; j++) {
+	    sources->data[j] = sources->data[j+1];
+	}
+	psFree (source);
+	sources->n --;
+    }
+
+    psFree (SN);
+    psFree (index);
+
+    psLogMsg ("psphot", PS_LOG_INFO, "found %d satstar blend peaks, leaving %ld sources: %f sec\n", Nblend, sources->n, psTimerMark ("psphot"));
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDefineFiles.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDefineFiles.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDefineFiles.c	(revision 22147)
@@ -0,0 +1,134 @@
+# include "psphotInternal.h"
+
+// XXX we need to be able to distinguish several cases:
+// 1) the particular output data was not requested
+// 2) the particular output data was requested but was not generated (skipped)
+// 3) the particular output data was requested but was not generated (for valid failure)
+// 4) the particular output data was requested but was not generated (surprising failure)
+
+// define the needed / desired I/O files
+bool psphotDefineFiles (pmConfig *config, pmFPAfile *input) {
+
+    bool status = false;
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    // the output sources are carried on the input->fpa structures
+    pmFPAfile *outsources = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.OUTPUT");
+    if (!outsources) {
+        psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.OUTPUT");
+        return false;
+    }
+    outsources->save = true;
+
+    // if we are choosing not to save the output detections, mark this file inactive so it will
+    // not be written, but will keep the detections in memory for other functions (eg, astrometry)
+    if (!psMetadataLookupBool (NULL, recipe, "SAVE.OUTPUT")) {
+        pmFPAfileActivate (config->files, false, "PSPHOT.OUTPUT");
+        outsources->save = false;
+    }
+
+    // optionally save the residual image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.RESID")) {
+        pmFPAfile *output = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.RESID");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.RESID");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background model (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL")) {
+        int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+        int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input, DX, DY, "PSPHOT.BACKMDL");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background model's standard deviation (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL.STDEV")) {
+        int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+        int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input, DX, DY, "PSPHOT.BACKMDL.STDEV");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL.STDEV");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the full background image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKGND")) {
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input,  1,  1, "PSPHOT.BACKGND");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKGND");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background-subtracted image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKSUB")) {
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input,  1,  1, "PSPHOT.BACKSUB");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PSF")) {
+        pmFPAfile *output = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.PSF.SAVE");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.PSF.SAVE");
+            return false;
+        }
+        output->save = true;
+    }
+
+    // optionally save output plots
+    // allow specific plots only
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PLOTS")) {
+        pmFPAfile *output = NULL;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.MOMENTS");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.MOMENTS");
+            return false;
+        }
+        output->save = true;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.PSFMODEL");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.PSFMODEL");
+            return false;
+        }
+        output->save = true;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.APRESID");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.APRESID");
+            return false;
+        }
+        output->save = true;
+    }
+
+    if (psMetadataLookupPtr(NULL, config->arguments, "SRC")) {
+        if (!pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT.CMF", "SRC")) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.INPUT.CMF");
+            return status;
+        }
+    }
+
+    if (psMetadataLookupPtr(NULL, config->arguments, "PSPHOT.PSF")) {
+        pmFPAfileBindFromArgs(&status, input, config, "PSPHOT.PSF.LOAD", "PSPHOT.PSF");
+        if (!status) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.PSF.LOAD");
+            return status;
+        }
+    }
+
+    // XXX add in example PSF image thumbnails
+    // pmFPAfileConstruct (config->files, format, config->camera, "PSPHOT.PSF_SAMPLE");
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDiagnosticPlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDiagnosticPlots.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotDiagnosticPlots.c	(revision 22147)
@@ -0,0 +1,189 @@
+# include "psphotInternal.h"
+
+# if (HAVE_KAPA)
+
+static int kapa_fd = -1;
+
+int psphotKapaOpen ()
+{
+    char kapa[64];
+
+    strcpy (kapa, "kapa");
+
+    if (kapa_fd == -1) {
+        kapa_fd = KapaOpenNamedSocket (kapa, "psphot");
+    }
+    return kapa_fd;
+}
+
+bool psphotKapaClose ()
+{
+
+    if (kapa_fd == -1)
+        return true;
+    KapaClose (kapa_fd);
+    kapa_fd = -1;
+    return true;
+}
+
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy)
+{
+
+    KapaSection section;
+    Graphdata graphdata;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    psStats *stats = psStatsAlloc (PS_STAT_MAX | PS_STAT_MIN);
+    psVectorStats (stats, values, NULL, NULL, 0);
+
+    psHistogram *histogram = psHistogramAlloc (stats->min, stats->max, 1000);
+    psVectorHistogram (histogram, values, NULL, NULL, 0);
+
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (kapa);
+
+    // plot 1 is the full range
+    section.x  = 0.0;
+    section.y  = 0.0;
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.name = strcreate ("bottom");
+    KapaSetSection (kapa, &section);
+    free (section.name);
+
+    // set limits based on data values
+    graphdata.xmin = stats->min;
+    graphdata.xmax = stats->max;
+
+    psStatsInit (stats);
+    stats->options = PS_STAT_MAX | PS_STAT_MIN;
+    psVectorStats (stats, histogram->nums, NULL, NULL, 0);
+
+    // scale the plot to hold the histogram
+    graphdata.ymin = stats->min - 0.05*(stats->max - stats->min);
+    graphdata.ymax = stats->max + 0.05*(stats->max - stats->min);
+
+    KapaSetLimits (kapa, &graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 1;
+    KapaPrepPlot (kapa, histogram->nums->n, &graphdata);
+    KapaPlotVector (kapa, histogram->nums->n, histogram->bounds->data.F32, "x");
+    KapaPlotVector (kapa, histogram->nums->n, histogram->nums->data.F32, "y");
+    psFree (histogram);
+
+    // plot 2 is the +/- 10 sigma
+    section.x  = 0.0;
+    section.y  = 0.5;
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.name = strcreate ("top");
+    KapaSetSection (kapa, &section);
+    free (section.name);
+
+    // +/- 10 sigma
+    graphdata.xmin = mean - 10.0*sigma;
+    graphdata.xmax = mean + 10.0*sigma;
+
+    histogram = psHistogramAlloc (graphdata.xmin, graphdata.xmax, 100);
+    psVectorHistogram (histogram, values, NULL, NULL, 0);
+
+    psStatsInit (stats);
+    stats->options = PS_STAT_MAX | PS_STAT_MIN;
+    psVectorStats (stats, histogram->nums, NULL, NULL, 0);
+
+    // scale the plot to hold the histogram
+    graphdata.ymin = stats->min - 0.05*(stats->max - stats->min);
+    graphdata.ymax = stats->max + 0.05*(stats->max - stats->min);
+
+    KapaSetLimits (kapa, &graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 1;
+    KapaPrepPlot (kapa, histogram->nums->n, &graphdata);
+    KapaPlotVector (kapa, histogram->nums->n, histogram->bounds->data.F32, "x");
+    KapaPlotVector (kapa, histogram->nums->n, histogram->nums->data.F32, "y");
+
+    char line[128];
+    sprintf (line, "sky: %f +/- %f, cell %d,%d", mean, sigma, ix, iy);
+    KapaSendLabel (kapa, line, KAPA_LABEL_XP);
+
+    // pause until user types 'return'
+    fprintf (stdout, "press return");
+    fgets (line, 64, stdin);
+
+    psFree (stats);
+    psFree (histogram);
+
+    return true;
+}
+
+bool psphotDiagnosticPlots (pmConfig *config, char *name, ...) {
+
+    va_list argPtr;
+    bool status;
+
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+    assert (plots);
+
+    // do we want the requested plot?
+    if (!psMetadataLookupBool (&status, plots, name)) return false;
+
+    // Get the variable list parameters to pass to allocation function
+    va_start(argPtr, name);
+
+    if (!strcmp(name, "IMAGE.BACKGROUND.CELL.HISTOGRAM")) {
+
+        int ix = va_arg(argPtr, psS32);
+        int iy = va_arg(argPtr, psS32);
+        float mean  = va_arg(argPtr, double);
+        float sigma = va_arg(argPtr, double);
+        psVector *values = va_arg(argPtr, psPtr);
+
+        int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+        assert (status);
+        int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+        assert (status);
+
+        bool gotX = (xPlot < 0) || (xPlot == ix);
+        bool gotY = (yPlot < 0) || (yPlot == iy);
+
+        if (gotX && gotY) {
+            psphotImageBackgroundCellHistogram (values, mean, sigma, ix, iy);
+            goto done;
+        }
+    }
+
+    // Clean up stack after variable arguement has been used
+done:
+    va_end(argPtr);
+    return true;
+}
+
+# else
+
+int psphotKapaOpen () { return -1; }
+bool psphotKapaClose () { return true; }
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy)
+{
+    return true;
+}
+bool psphotDiagnosticPlots (pmConfig *config, char *name, ...) { return true; }
+
+# endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.c.in	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.c.in	(revision 22147)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PSPHOT_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "psphotErrorCodes.h"
+
+void psphotErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSPHOT_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSPHOT_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSPHOT_ERR_NERROR - PSPHOT_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.dat	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.dat	(revision 22147)
@@ -0,0 +1,17 @@
+#
+# This file is used to generate psphotErrorClasses.h
+#
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+FITS			Problem in FITS I/O
+FITS_WCS		Error interpreting FITS WCS information
+PHOTOM			Problem in photometry
+PSF			Problem in PSF
+APERTURE		Problem with aperture photometry
+SKY			Problem in sky determination
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.h.in	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotErrorCodes.h.in	(revision 22147)
@@ -0,0 +1,18 @@
+#if !defined(PSPHOT_ERROR_CODES_H)
+#define PSPHOT_ERROR_CODES_H
+/*
+ * The line
+ *  PSPHOT_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSPHOT_ERR_BASE = 1300,
+    PSPHOT_ERR_${ErrorCode},
+    PSPHOT_ERR_NERROR
+} psphotErrorCode;
+
+void psphotErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotEvalFLT.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotEvalFLT.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotEvalFLT.c	(revision 22147)
@@ -0,0 +1,47 @@
+# include "psphotInternal.h"
+
+bool psphotEvalEXT (pmSource *source, pmModel *model)
+{ 
+    int keep;
+
+    // do we actually have a valid EXT model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	return false;
+    }
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	psLogMsg ("psphot", 5, "EXT fail fit\n");
+	return false;
+    }
+
+    // unless we prove otherwise, this object is extended
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+
+    // the following source->mode information pertains to modelEXT:
+    source->mode |= PM_SOURCE_MODE_EXTMODEL;
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    // XXX this limit is fairly arbitrary, and must be > the value is the model limits
+    if (model->params->data.F32[PM_PAR_I0] <= 0.02) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    keep = model->modelFitStatus(model);
+    if (keep) return true;
+
+    // poor-quality fit; only keep if nothing else works...
+    psLogMsg ("psphot", 5, "EXT poor fit\n");
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotEvalPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotEvalPSF.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotEvalPSF.c	(revision 22147)
@@ -0,0 +1,238 @@
+# include "psphotInternal.h"
+
+// given a pmSource which has been fitted using modelPSF, evaluate the
+// resulting fit: did the fit succeed? is this object PSF-like? is this object 
+// extended?  return status is TRUE for a valid PSF, FALSE otherwise.
+
+// identify objects consistent with PSF shape/magnitude distribution
+// we expect dparams[PM_PAR_SXX],dparams[PM_PAR_SXX] to have a scatter of:
+// sigma_x / (S/N)
+
+// any objects which is consistent with the PSF should have 
+// abs(dparams[PM_PAR_SXX]) < N * dsxLine(mag) & abs(dparams[PM_PAR_SYY]) < N * dsyLine(mag)
+// this includes a minimum buffer (DS) for the brighter objects
+
+// EAM : 2006.10.20 : I have re-defined params[PM_PAR_SXX] : it is now sqrt(2)*sigma_x, not 1/sigma_x
+
+// saturated stars should fall outside (larger), but have peaks above SATURATION
+// extended sources should be larger, cosmic rays smaller
+// we also reject objects with S/N too low or ChiSquare to high
+
+// floor for DS value 
+// XXX EAM : add to configuration?
+
+static float SATURATION;
+static float PSF_MIN_SN;
+static float PSF_MIN_DS;
+static float PSF_MAX_CHI;
+static float PSF_SHAPE_NSIGMA;
+static float PSF_DSX_MEAN;
+static float PSF_DSY_MEAN;
+static float PSF_DSX_STDEV;
+static float PSF_DSY_STDEV;
+
+bool psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout) {
+
+    bool status;
+
+    // XXX do we need to set this differently from the value used to mark saturated pixels?
+    pmCell *cell     = readout->parent;
+    SATURATION       = psMetadataLookupF32 (&status, cell->concepts, "CELL.SATURATION");
+    PSF_MIN_SN       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_SN");
+    PSF_MAX_CHI      = psMetadataLookupF32 (&status, recipe, "PSF_MAX_CHI");
+    PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_SHAPE_NSIGMA");
+    PSF_MIN_DS       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_DS");
+    if (!status) PSF_MIN_DS = 0.01;
+
+    PSF_DSX_MEAN     = psMetadataLookupF32 (&status, recipe, "DSX_MEAN");
+    PSF_DSY_MEAN     = psMetadataLookupF32 (&status, recipe, "DSY_MEAN");
+    PSF_DSX_STDEV    = psMetadataLookupF32 (&status, recipe, "DSX_STDV");
+    PSF_DSY_STDEV    = psMetadataLookupF32 (&status, recipe, "DSY_STDV");
+
+    return true;
+}
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalPSF (pmSource *source, pmModel *model) { 
+
+    int keep;
+    float dSX, dSY, SX, SY, SN;
+    float nSx, nSy, Chi;
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar was not fitted");
+	}
+	return false;
+    }
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar failed fit");
+	}
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0.02, the source is not viable
+    // perhaps the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0.02) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar failed fit (peak below 0)");
+	}
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+
+    SN  = model->params->data.F32[PM_PAR_I0]/model->dparams->data.F32[PM_PAR_I0];
+    SX  = model->params->data.F32[PM_PAR_SXX];
+    SY  = model->params->data.F32[PM_PAR_SYY];
+    dSX = model->dparams->data.F32[PM_PAR_SXX];
+    dSY = model->dparams->data.F32[PM_PAR_SYY];
+    Chi = model->chisqNorm / model->nDOF;
+
+    // swing of sigma_x,y in sigmas
+    nSx = (dSX - PSF_DSX_MEAN) / (PSF_DSX_STDEV * PS_MAX (PSF_MIN_DS, (SX / SN)));
+    nSy = (dSY - PSF_DSY_MEAN) / (PSF_DSY_STDEV * PS_MAX (PSF_MIN_DS, (SY / SN)));
+
+    // assign PM_SOURCE_MODE_GOODSTAR to bright objects within PSF region of dparams[]
+    keep = TRUE;
+    // keep &= (fabs(nSx) < PSF_SHAPE_NSIGMA);
+    // keep &= (fabs(nSy) < PSF_SHAPE_NSIGMA);
+    keep &= (SN > PSF_MIN_SN);
+    keep &= (Chi < PSF_MAX_CHI);
+
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psTrace ("psphot", 5, "satstar fit results: %f, %f  %d :  %f %f : %f %f : %f %f\n", 
+		     model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], keep,
+		 dSX, nSx, dSY, nSy, SN, Chi);
+    }
+
+    if (keep) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psTrace ("psphot", 7, "PSFSTAR kept (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		     model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+	}
+	return true;
+    }
+
+    // this source is not a star, warn if it was a PSFSTAR
+    if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	psTrace ("psphot", 5, "PSFSTAR demoted based on fit quality   (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+    } else {
+	psTrace ("psphot", 5, "fails PSF fit (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+    }
+
+    // object appears to be small, suspected defect.  this is a weak indication for a defect: a
+    // large positive or negative swing can also come from the bright sources for which we are 
+    // more sensitive to the wings than for the faint sources.  
+    // XXX allow -NSIGMA and +NSIGMA to have different values?
+    if ((nSx <= -PSF_SHAPE_NSIGMA) || (nSy <= -PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_DEFECT;
+	return false;
+    }
+
+    // object appears to be large, suspected extended source
+    if ((nSx >= PSF_SHAPE_NSIGMA) || (nSy >= PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_EXTENDED;
+	return false;
+    }
+
+    // poor-quality fit; only keep if nothing else works...
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalDBL (pmSource *source, pmModel *model) { 
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	return false;
+    }
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+    return true;
+}	
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotExtendedSources.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotExtendedSources.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotExtendedSources.c	(revision 22147)
@@ -0,0 +1,127 @@
+# include "psphot.h"
+
+bool psphotExtendedSources (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status;
+    int Next = 0;
+    int Npsf = 0;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // S/N limit to perform full non-linear fits
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+	return true;
+    }
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // 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 doPSFConvModel = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PSF_CONVOLVED_MODEL");
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // XXX some init functions for the extended source recipe options?
+
+    // choose the sources of interest
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        // skip PSF-like and non-astronomical objects
+        if (source->type == PM_SOURCE_TYPE_STAR) continue;
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // limit selection to some SN limit
+        assert (source->peak); // how can a source not have a peak?
+        if (source->peak->SN < SN_LIM) continue;
+
+        // XXX this should use peak?
+        if (source->peak->x < AnalysisRegion.x0) continue;
+        if (source->peak->y < AnalysisRegion.y0) continue;
+        if (source->peak->x > AnalysisRegion.x1) continue;
+        if (source->peak->y > AnalysisRegion.y1) continue;
+
+        // if model is NULL, we don't have a starting guess
+        if (source->modelEXT == NULL) continue;
+
+        // replace object in image
+        if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+        Next ++;
+
+	if (doPSFConvModel && !psphotPSFConvModel (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in PSF Convolved Model fit");
+	    return false;
+	} else {
+	  // XXX why am I caching the model?
+	    pmSourceCacheModel (source, maskVal); // XXX put this in the source model function?
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Npsf ++;
+	}
+
+	// all of these below require the radial profile
+	// XXX push this as a test and call in each of the functions below?
+	// XXX this constructs a pmSourceExtendedParameters element
+	if (doPetrosian || doIsophotal || doAnnuli || doKron) {
+	  if (!psphotRadialProfile (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate radial profile");
+	    return false;
+	  }
+	}
+
+	if (doPetrosian && !psphotPetrosian (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Petrosian analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Npsf ++;
+	}
+
+	if (doIsophotal && !psphotIsophotal (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Isophotal analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Npsf ++;
+	}
+
+	if (doAnnuli && !psphotAnnuli (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Annuli analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Npsf ++;
+	}
+
+	if (doKron && !psphotKron (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Kron analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Npsf ++;
+	}
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot"), Next);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFakeSources.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFakeSources.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFakeSources.c	(revision 22147)
@@ -0,0 +1,30 @@
+# include "psphotInternal.h"
+
+psArray *psphotFakeSources () {
+
+    // psphotUpdateHeader (header, config);
+
+    psArray *sources = psArrayAlloc (50);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = pmSourceAlloc ();
+        source->moments = pmMomentsAlloc ();
+        source->moments->x = 10;
+        source->moments->y = 10;
+        source->moments->Sx = 1;
+        source->moments->Sy = 1;
+        source->moments->Sxy = 0;
+        source->moments->Sum = 1000;
+        source->moments->Peak = 100;
+        source->moments->Sky = 10;
+        source->moments->nPixels = 10;
+
+        source->peak = pmPeakAlloc (10, 10, 0, 0);
+        source->type = PM_SOURCE_TYPE_STAR;
+
+        pmModelType modelType = pmModelClassGetType ("PS_MODEL_QGAUSS");
+        source->modelPSF = pmSourceModelGuess (source, modelType);
+        sources->data[i] = source;
+    }
+    return sources;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFindDetections.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFindDetections.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFindDetections.c	(revision 22147)
@@ -0,0 +1,78 @@
+# include "psphotInternal.h"
+
+// XXX let's make a better distinction between 'pass' (used for counting intput / output
+// entries) and needing/having a PSF.  In this function, we smooth the image, then search for
+// the peaks
+pmDetections *psphotFindDetections (pmDetections *detections, pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+    int pass;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // Use the new pmFootprints approach?
+    const bool useFootprints = psMetadataLookupBool(NULL, recipe, "USE_FOOTPRINTS");
+
+    // on first pass, detections have not yet been allocated
+    if (!detections) {
+	detections = pmDetectionsAlloc();
+	pass = 1;
+    } else {
+	pass = 2;
+    }
+
+    // move the old peaks array (if it exists) to oldPeaks 
+    assert (detections->oldPeaks == NULL);
+    detections->oldPeaks = detections->peaks;
+    detections->peaks = NULL;
+
+    // if footprints are not requested, find the peaks and return them
+    if (!useFootprints) {
+	detections->peaks = psphotFindPeaks (readout, recipe, useFootprints, pass, maskVal);
+	return detections;
+    }
+
+    // psphotFindPeaks returns a different set of objects if useFootprints is true!
+    // XXX fix this by splitting psphotFindPeaks into smooth / peaks / footprint stages?
+    psArray *footprints = psphotFindPeaks (readout, recipe, useFootprints, pass, maskVal);
+
+    int growRadius = 0;
+    if (pass == 1) {
+	growRadius = psMetadataLookupS32(NULL, recipe, "FOOTPRINT_GROW_RADIUS");
+    } else {
+	growRadius = psMetadataLookupS32(NULL, recipe, "FOOTPRINT_GROW_RADIUS_2");
+    }
+
+    if (growRadius > 0) {
+	psArray *tmp = pmGrowFootprintArray(footprints, growRadius);
+	psFree(footprints);
+	footprints = tmp;
+    }
+
+    if (pass == 2) {
+	// merge in old peaks;
+	const int includePeaks = 0x0 | 0x2; // i.e. just from newFootprints
+	
+	psLogMsg ("psphot", PS_LOG_INFO, "merging %ld new footprints into %ld existing ones\n", footprints->n, detections->footprints->n);
+	psArray *mergedFootprints = pmMergeFootprintArrays(detections->footprints, footprints, includePeaks);
+	psFree(footprints);
+	psFree(detections->footprints);
+	detections->footprints = mergedFootprints;
+    } else {
+	detections->footprints = footprints;
+    }
+
+    psphotCullPeaks(readout->image, readout->weight, recipe, detections->footprints);
+    detections->peaks = pmFootprintArrayToPeaks(detections->footprints);
+    psLogMsg ("psphot", PS_LOG_INFO, "peaks: %ld, total footprints: %ld\n", detections->peaks->n, detections->footprints->n);
+
+    return detections;
+}
+
+// if we use the footprints, the output peaks list contains both old and new peaks,
+// otherwise it only contains the new peaks.
+
+// if psf is defined, we should treat this as pass "2", not "1".  re-define this boolean to
+// be "have PSF"
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFindPeaks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFindPeaks.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFindPeaks.c	(revision 22147)
@@ -0,0 +1,177 @@
+# include "psphotInternal.h"
+
+// XXX let's make a better distinction between 'pass' and needing/having a PSF.
+// In this function, we smooth the image, then search for the peaks
+psArray *psphotFindPeaks (pmReadout *readout, psMetadata *recipe,
+                          bool returnFootprints,
+                          const int pass, psMaskType maskVal) {
+
+    float SIGMA_SMTH, NSIGMA_SMTH, NSIGMA_PEAK;
+    bool  status = false;
+
+    // smooth the image and weight map
+    psTimerStart ("psphot");
+
+    // XXX if we have been supplied a PSF, we can use that to set the FWHM_X,FWHM_Y values
+    if (pass == 1) {
+        SIGMA_SMTH  = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_SIGMA");
+        NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+    } else {
+        bool status_x, status_y;
+        float FWHM_X = psMetadataLookupF32 (&status_x, recipe, "FWHM_X");
+        float FWHM_Y = psMetadataLookupF32 (&status_y, recipe, "FWHM_Y");
+        if (!status_x | !status_y) {
+            psError(PSPHOT_ERR_CONFIG, false, "FWHM_X or FWHM_Y not defined");
+            return false;
+        }
+        SIGMA_SMTH  = 0.5*(FWHM_X + FWHM_Y) / (2.0*sqrt(2.0*log(2.0)));
+        NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+    }
+
+    // smooth the image, applying the mask as we go
+    psImage *smooth_im = psImageCopy (NULL, readout->image, PS_TYPE_F32);
+    psImageSmoothMaskF32 (smooth_im, readout->mask, maskVal, SIGMA_SMTH, NSIGMA_SMTH);
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "smooth image: %f sec\n", psTimerMark ("psphot"));
+
+    // smooth the weight, applying the mask as we go
+    psImage *smooth_wt = psImageCopy (NULL, readout->weight, PS_TYPE_F32);
+    psImageSmoothMaskF32 (smooth_wt, readout->mask, maskVal, SIGMA_SMTH/sqrt(2), NSIGMA_SMTH);
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "smooth weight: %f sec\n", psTimerMark ("psphot"));
+
+    psImage *mask = readout->mask;
+
+    // optionally save example images under trace
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[64];
+        sprintf (name, "imsmooth.v%d.fits", pass);
+        psphotSaveImage (NULL, smooth_im, name);
+        sprintf (name, "wtsmooth.v%d.fits", pass);
+        psphotSaveImage (NULL, smooth_wt, name);
+    }
+
+    // build the significance image on top of smooth_im
+    for (int j = 0; j < smooth_im->numRows; j++) {
+        for (int i = 0; i < smooth_im->numCols; i++) {
+            float value = smooth_im->data.F32[j][i];
+            if (value < 0 || smooth_wt->data.F32[j][i] <= 0 || (mask->data.U8[j][i] & maskVal)) {
+                smooth_im->data.F32[j][i] = 0.0;
+            } else {
+                smooth_im->data.F32[j][i] = PS_SQR(value) / smooth_wt->data.F32[j][i];
+            }
+        }
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "built smoothed signficance image: %f sec\n", psTimerMark ("psphot"));
+
+    // optionally save example images under trace
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[64];
+        sprintf (name, "snsmooth.v%d.fits", pass);
+        psphotSaveImage (NULL, smooth_im, name);
+    }
+
+    psTimerStart ("psphot");
+    // set peak threshold
+
+    // signal/noise limit for the detected peaks
+    if (pass == 1) {
+        NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT");
+    } else {
+        NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT_2");
+    }
+
+    // we need to define the threshold based on the value of NSIGMA_PEAK and the applied smoothing
+    // gaussian SIGMA.  a peak in the significance image has an effective S/N for faint sources
+    // of (S = Io*2pi*sigma_eff^2) / sqrt(Var), where sigma_eff^2 = sigma_obs^2 + sigma_sm^2.
+    // the smoothing of the varience map does not affect the faint-source S/N since the noise
+    // is constant under a faint source.
+
+    // if sigma_sm = sigma_obs, then sigma_eff^2 = 2 sigma_sm^2. in this case, the threshold in
+    // terms of smooth_im peak counts Io, for a desired S/N limit corresponds to
+    // S/N = sqrt(Io)*4*pi*sigma_sm^2
+    // thus, the threshold is:
+    float effArea = 4.0*M_PI*PS_SQR(SIGMA_SMTH);
+    if (effArea < 1) {
+        effArea = 1;                    // never less than a pixel
+    }
+    if (pass == 1) {
+	effArea = 4.0*M_PI; // XXX better than nothing but still fairly poor
+    } 
+
+    float threshold = PS_SQR(NSIGMA_PEAK) / effArea;
+
+    // find the peaks in the smoothed image
+    psArray *peaks = pmPeaksInImage (smooth_im, threshold);
+    if (peaks == NULL) {
+        // XXX this may also be due to a programming or config error
+        // XXX do we need to set something in the readout->analysis to indicate that
+        // we tried and failed to find peaks (something in the header data)
+        psError(PSPHOT_ERR_DATA, false, "no peaks found in this image");
+        return false;
+    }
+
+    // correct the peak values to S/N = sqrt(value*effArea)
+    // get the peak flux from the unsmoothed image
+    // the peak pixel coords are guaranteed to be on the image
+    int row0 = readout->image->row0;
+    int col0 = readout->image->col0;
+    for (int i = 0; i < peaks->n; i++) {
+        pmPeak *peak = peaks->data[i];
+        peak->SN = sqrt(peak->value*effArea);
+        peak->flux = readout->image->data.F32[peak->y-row0][peak->x-col0];
+    }
+
+    // limit the total number of returned peak as specified
+    if (pass == 1) {
+        psArraySort (peaks, pmPeakSortBySN);
+        int NMAX = psMetadataLookupS32 (&status, recipe, "PEAKS_NMAX");
+        if (NMAX && (peaks->n > NMAX)) {
+            psArray *tmpPeaks = psArrayAllocEmpty (NMAX);
+            for (int i = 0; i < NMAX; i++) {
+                psArrayAdd (tmpPeaks, 100, peaks->data[i]);
+            }
+            psFree (peaks);
+            peaks = tmpPeaks;
+        }
+    }
+
+    // optional dump of all peak data
+    char *output = psMetadataLookupStr (&status, recipe, "PEAKS_OUTPUT_FILE");
+    if (status && (output != NULL) && (output[0])) {
+        pmPeaksWriteText (peaks, output);
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks: %f sec\n", peaks->n, psTimerMark ("psphot"));
+
+    // If they asked us to return a set of pmFootprints, not a raw pmPeak list,
+    // go ahead and find the footprints, and assign them their peaks.
+    //
+    // N.b. We're not culling this list; call pmFootprintCullPeaks if you
+    // want to do that
+    //
+    if (returnFootprints) {     // We want an array of pmFootprint, not pmPeak
+        int npixMin = psMetadataLookupS32(&status, recipe, "FOOTPRINT_NPIXMIN");
+        if (!status) {
+            npixMin = 1;
+        }
+        float FOOTPRINT_NSIGMA_LIMIT =
+            psMetadataLookupS32(&status, recipe,
+                                (pass == 1) ? "FOOTPRINT_NSIGMA_LIMIT" : "FOOTPRINT_NSIGMA_LIMIT_2");
+        if (!status) {
+            FOOTPRINT_NSIGMA_LIMIT = NSIGMA_PEAK;
+        }
+        threshold = PS_SQR(FOOTPRINT_NSIGMA_LIMIT)/effArea;
+
+        psArray *footprints = pmFindFootprints(smooth_im, threshold, npixMin);
+        pmPeaksAssignToFootprints(footprints, peaks);
+
+        psFree(peaks);
+        peaks = footprints;             // well, you know what I mean
+
+	psLogMsg ("psphot", PS_LOG_INFO, "%ld footprints: %f sec\n", peaks->n, psTimerMark ("psphot"));
+    }
+
+    psFree (smooth_im);
+    psFree (smooth_wt);
+
+    return (peaks);
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFitSet.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFitSet.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFitSet.c	(revision 22147)
@@ -0,0 +1,48 @@
+# include "psphotInternal.h"
+
+// This is only used by psphotModelTest.c
+bool psphotFitSet (pmSource *source, pmModel *oneModel, char *fitset, pmSourceFitMode mode, psMaskType maskVal) {
+
+    double x, y, Io;
+
+    FILE *f = fopen (fitset, "r");
+    if (f == NULL) return false;
+
+    psArray *modelSet = psArrayAllocEmpty (16);
+
+    while (fscanf (f, "%lf %lf %lf", &x, &y, &Io) == 3) {
+        pmModel *model = pmModelAlloc (oneModel->type);
+
+        for (psS32 i = 0; i < model->params->n; i++) {
+            model->params->data.F32[i] = oneModel->params->data.F32[i];
+            model->dparams->data.F32[i] = oneModel->dparams->data.F32[i];
+        }
+        model->params->data.F32[1] = Io;
+        model->params->data.F32[2] = x;
+        model->params->data.F32[3] = y;
+        psArrayAdd (modelSet, 16, model);
+    }
+
+    // XXX pmSourceFitSet must cache the modelFlux?
+    pmSourceFitSet (source, modelSet, mode, maskVal);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    for (int i = 0; i < modelSet->n; i++) {
+        pmModel *model = modelSet->data[i];
+        pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+
+        fprintf (stderr, "output parameters (obj %d):\n", i);
+        for (int n = 0; n < model->params->n; n++) {
+            fprintf (stderr, "%d : %f\n", n, model->params->data.F32[n]);
+        }
+    }
+
+    // write out
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFitSourcesLinear.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFitSourcesLinear.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotFitSourcesLinear.c	(revision 22147)
@@ -0,0 +1,290 @@
+# include "psphotInternal.h"
+
+// fit flux (and optionally sky model) to all reasonable sources
+// with the linear fitting process.  sources must have an associated
+// model with selected pixels, and the fit radius must be defined
+
+// given the set of sources, each of which points to the pixels in the
+// science image, we construct a set of simulated sources with their own pixels.
+// these are used to determine the simultaneous linear fit of fluxes.
+// the analysis is performed wrt the simulated pixel values
+
+static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER);
+
+bool psphotFitSourcesLinear (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final) {
+
+    bool status;
+    float x;
+    float y;
+    float f;
+    // float r;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // source analysis is done in spatial order
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // storage array for fitSources
+    psArray *fitSources = psArrayAllocEmpty (sources->n);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionFromString (region);
+    AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    bool CONSTANT_PHOTOMETRIC_WEIGHTS =
+        psMetadataLookupBool(&status, recipe, "CONSTANT_PHOTOMETRIC_WEIGHTS");
+    if (!status) {
+        psAbort("You must provide a value for the BOOL recipe CONSTANT_PHOTOMETRIC_WEIGHTS");
+    }
+    int SKY_FIT_ORDER = psMetadataLookupS32(&status, recipe, "SKY_FIT_ORDER");
+    if (!status) {
+        SKY_FIT_ORDER = 0;
+    }
+    bool SKY_FIT_LINEAR = psMetadataLookupBool(&status, recipe, "SKY_FIT_LINEAR");
+    if (!status) {
+        SKY_FIT_LINEAR = false;
+    }
+
+    // select the sources which will be used for the fitting analysis
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+        if (source->type == PM_SOURCE_TYPE_STAR &&
+            source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+        if (final) {
+            if (source->mode &  PM_SOURCE_MODE_SUBTRACTED) continue;
+        } else {
+            if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        }
+
+	// generate model for sources without, or skip if we can't
+	if (!source->modelFlux) {
+	    if (!pmSourceCacheModel (source, maskVal)) continue;
+	}
+
+        // save the original coords
+        x = source->peak->xf;
+        y = source->peak->yf;
+
+        // is the source in the region of interest?
+        if (x < AnalysisRegion.x0) continue;
+        if (y < AnalysisRegion.y0) continue;
+        if (x > AnalysisRegion.x1) continue;
+        if (y > AnalysisRegion.y1) continue;
+
+        psArrayAdd (fitSources, 100, source);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built fitSources: %f (%ld objects)\n", psTimerMark ("psphot"), sources->n);
+
+    // vectors to store stats for each object
+    // psVector *weight = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+    psVector *errors = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+
+    // create the border matrix (includes the sparse matrix)
+    // for just sky: 1 row; for x,y terms: 3 rows
+    psSparse *sparse = psSparseAlloc (fitSources->n, 100);
+    int nBorder = (SKY_FIT_ORDER == 0) ? 1 : 3;
+    psSparseBorder *border = psSparseBorderAlloc (sparse, nBorder);
+
+    // fill out the sparse matrix elements and border elements (B)
+    // SRCi is the current source of interest
+    // SRCj is a possibly overlapping source
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *SRCi = fitSources->data[i];
+
+        // diagonal elements of the sparse matrix (auto-cross-product)
+        f = pmSourceModelDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+        psSparseMatrixElement (sparse, i, i, f);
+
+        // the formal error depends on the weighting scheme
+        if (CONSTANT_PHOTOMETRIC_WEIGHTS) {
+            float var = pmSourceModelDotModel (SRCi, SRCi, false);
+            errors->data.F32[i] = 1.0 / sqrt(var);
+        } else {
+            errors->data.F32[i] = 1.0 / sqrt(f);
+        }
+
+
+        // find the image x model value
+        f = pmSourceDataDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+        psSparseVectorElement (sparse, i, f);
+
+        // add the per-source weights (border region)
+        switch (SKY_FIT_ORDER) {
+          case 1:
+            f = pmSourceModelWeight (SRCi, 1, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseBorderElementB (border, i, 1, f);
+            f = pmSourceModelWeight (SRCi, 2, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseBorderElementB (border, i, 2, f);
+
+          case 0:
+            f = pmSourceModelWeight (SRCi, 0, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseBorderElementB (border, i, 0, f);
+            break;
+
+          default:
+            psAbort("Invalid SKY_FIT_ORDER %d\n", SKY_FIT_ORDER);
+            break;
+        }
+
+        // loop over all other stars following this one
+        for (int j = i + 1; j < fitSources->n; j++) {
+            pmSource *SRCj = fitSources->data[j];
+
+            // skip over disjoint source images, break after last possible overlap
+            if (SRCi->pixels->row0 + SRCi->pixels->numRows < SRCj->pixels->row0) break;
+            if (SRCj->pixels->row0 + SRCj->pixels->numRows < SRCi->pixels->row0) continue;
+            if (SRCi->pixels->col0 + SRCi->pixels->numCols < SRCj->pixels->col0) continue;
+            if (SRCj->pixels->col0 + SRCj->pixels->numCols < SRCi->pixels->col0) continue;
+
+            // got an overlap; calculate cross-product and add to output array
+            f = pmSourceModelDotModel (SRCi, SRCj, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseMatrixElement (sparse, j, i, f);
+        }
+    }
+
+    psSparseResort (sparse);
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built matrix: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    // set the sky, sky_x, sky_y components of border matrix
+    SetBorderMatrixElements (border, readout, fitSources, CONSTANT_PHOTOMETRIC_WEIGHTS, SKY_FIT_ORDER);
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = 0.0;
+    constraint.paramMax   = 1e8;
+    constraint.paramDelta = 1e8;
+
+    // solve for normalization terms (need include local sky?)
+    psVector *norm = NULL;
+    psVector *skyfit = NULL;
+    if (SKY_FIT_LINEAR) {
+        psSparseBorderSolve (&norm, &skyfit, constraint, border, 5);
+        fprintf (stderr, "skyfit: %f\n", skyfit->data.F32[0]);
+    } else {
+        norm = psSparseSolve (NULL, constraint, sparse, 5);
+        skyfit = NULL;
+    }
+
+    // adjust I0 for fitSources and subtract
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *source = fitSources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+
+        // assign linearly-fitted normalization
+        if (isnan(norm->data.F32[i])) {
+            psAbort("linear fitted source is nan");
+        }
+        model->params->data.F32[PM_PAR_I0] = norm->data.F32[i];
+        model->dparams->data.F32[PM_PAR_I0] = errors->data.F32[i];
+        // XXX is the value of 'errors' modified by the sky fit?
+
+        // subtract object
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+
+    // measure chisq for each source
+    for (int i = 0; final && (i < fitSources->n); i++) {
+        pmSource *source = fitSources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        pmSourceChisq (model, source->pixels, source->maskObj, source->weight, maskVal);
+    }
+
+    // psFree (index);
+    psFree (sparse);
+    psFree (fitSources);
+    psFree (norm);
+    psFree (skyfit);
+    psFree (errors);
+    psFree (border);
+
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
+// Calculate the weight terms for the sky fit component of the matrix.  This function operates
+// on the pixels which correspond to all of the sources of interest.  These elements fill in
+// the border matrix components in the sparse matrix equation.
+static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER) {
+
+    // generate the image-wide weight terms
+    // turn on MARK for all image pixels
+    psRegion fullArray = psRegionSet (0, 0, 0, 0);
+    fullArray = psRegionForImage (readout->mask, fullArray);
+    psImageMaskRegion (readout->mask, fullArray, "OR", PM_MASK_MARK);
+
+    // turn off MARK for all object pixels
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL) continue;
+        float x = model->params->data.F32[PM_PAR_XPOS];
+        float y = model->params->data.F32[PM_PAR_YPOS];
+        psImageMaskCircle (source->maskView, x, y, model->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+    }
+
+    // accumulate the image statistics from the masked regions
+    psF32 **image  = readout->image->data.F32;
+    psF32 **weight = readout->weight->data.F32;
+    psU8  **mask   = readout->mask->data.U8;
+
+    double w, x, y, x2, xy, y2, xc, yc, wt, f, fo, fx, fy;
+    w = x = y = x2 = xy = y2 = fo = fx = fy = 0;
+
+    int col0 = readout->image->col0;
+    int row0 = readout->image->row0;
+
+    for (int j = 0; j < readout->image->numRows; j++) {
+        for (int i = 0; i < readout->image->numCols; i++) {
+            if (mask[j][i]) continue;
+            if (constant_weights) {
+                wt = 1.0;
+            } else {
+                wt = weight[j][i];
+            }
+            f = image[j][i];
+            w   += 1/wt;
+            fo  += f/wt;
+            if (SKY_FIT_ORDER == 0) continue;
+
+            xc  = i + col0;
+            yc  = j + row0;
+            x  +=    xc/wt;
+            y  +=    yc/wt;
+            x2 += xc*xc/wt;
+            xy += xc*yc/wt;
+            y2 += yc*yc/wt;
+            fx +=  f*xc/wt;
+            fy +=  f*yc/wt;
+        }
+    }
+
+    // turn off MARK for all image pixels
+    psImageMaskRegion (readout->mask, fullArray, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    // set the Border T elements
+    psSparseBorderElementG (border, 0, fo);
+    psSparseBorderElementT (border, 0, 0, w);
+    if (SKY_FIT_ORDER > 0) {
+        psSparseBorderElementG (border, 0, fx);
+        psSparseBorderElementG (border, 0, fy);
+        psSparseBorderElementT (border, 1, 0, x);
+        psSparseBorderElementT (border, 2, 0, y);
+        psSparseBorderElementT (border, 0, 1, x);
+        psSparseBorderElementT (border, 1, 1, x2);
+        psSparseBorderElementT (border, 2, 1, xy);
+        psSparseBorderElementT (border, 0, 2, y);
+        psSparseBorderElementT (border, 1, 2, xy);
+        psSparseBorderElementT (border, 2, 2, y2);
+    }
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotGrowthCurve.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotGrowthCurve.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotGrowthCurve.c	(revision 22147)
@@ -0,0 +1,70 @@
+# include "psphotInternal.h"
+
+// XXX we can probably move this into pmGrowthCurve.c
+// XXX need to change the way we grab an image, or else use
+//     the 'center' option
+
+// XXX add a option to turn off the curve-of-growth (ie, make the apMag = fitMag everywhere);
+
+bool psphotGrowthCurve (pmReadout *readout, pmPSF *psf, bool ignore, psMaskType maskVal) {
+
+    // bool status;
+    float xc, yc, dx, dy;
+    float fitMag, apMag;
+    float radius;
+
+    // use the center of the center pixel of the image
+    xc = 0.5*readout->image->numCols + readout->image->col0 + 0.5;
+    yc = 0.5*readout->image->numRows + readout->image->row0 + 0.5;
+    dx = psf->growth->maxRadius + 1;
+    dy = psf->growth->maxRadius + 1;
+
+    // create normalized model object at xc,yc 
+    pmModel *model = pmModelFromPSFforXY (psf, xc, yc, 1.0);
+
+    // measure the fitMag for this model
+    pmSourcePhotometryModel (&fitMag, model);
+    psf->growth->fitMag = fitMag;
+
+    // generate working image for this source
+    psRegion region = {xc - dx, xc + dx, yc - dy, yc + dy};
+    psImage *view = psImageSubset (readout->image, region);
+    psImage *image = psImageCopy (NULL, view, PS_TYPE_F32);
+    psImage *mask = psImageCopy (NULL, view, PS_TYPE_U8);
+    psImageInit (mask, 0);
+
+    // loop over a range of source fluxes
+    // no need to interpolate since we have force the object center
+    // to 0.5, 0.5 above
+    for (int i = 0; i < psf->growth->radius->n; i++) {
+
+        psImageInit (image, 0.0);
+
+        radius = psf->growth->radius->data.F32[i];
+
+        // NOTE: we use pmModelAdd not pmSourceAdd because we are not working with a normal source
+        psImageKeepCircle (mask, xc, yc, radius, "OR", PM_MASK_MARK);
+        pmModelAdd (image, mask, model, PM_MODEL_OP_FULL, maskVal);
+        pmSourcePhotometryAper (&apMag, model, image, mask, maskVal);
+        psImageKeepCircle (mask, xc, yc, radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+        // the 'ignore' mode is for testing
+        if (ignore) {
+            psf->growth->apMag->data.F32[i] = fitMag;
+        } else {
+            psf->growth->apMag->data.F32[i] = apMag;
+        }
+    }
+
+    psf->growth->apRef = psVectorInterpolate (psf->growth->radius, psf->growth->apMag, psf->growth->refRadius);
+    psf->growth->apLoss = psf->growth->fitMag - psf->growth->apRef;
+
+    psLogMsg ("psphot.growth", 4, "GrowthCurve : apLoss : %f\n", psf->growth->apLoss);
+
+    psFree (view);
+    psFree (image);
+    psFree (mask);
+    psFree (model);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotGuessModels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotGuessModels.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotGuessModels.c	(revision 22147)
@@ -0,0 +1,99 @@
+# include "psphotInternal.h"
+
+// A guess for when the moments aren't available
+static pmModel *wildGuess(pmSource *source, // Source for which to guess
+                          pmPSF *psf    // The point-spread function
+    )
+{
+    pmModel *model = pmModelAlloc(psf->type);
+    psF32 *PAR = model->params->data.F32;
+    PAR[PM_PAR_SKY]  = 0;
+    // XXX get this from the image pixels
+    PAR[PM_PAR_I0]   = source->peak->flux;
+    PAR[PM_PAR_XPOS] = source->peak->xf;
+    PAR[PM_PAR_YPOS] = source->peak->yf;
+    return model;
+}
+
+// construct an initial PSF model for each object
+bool psphotGuessModels (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // setup the PSF fit radius details
+    psphotInitRadiusPSF (recipe, psf->type);
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+	// XXX if a source is faint, it will not have moments measured.
+	// it must be modelled as a PSF.  In this case, we need to use
+	// the peak centroid to get the coordinates and get the peak flux
+	// from the image?
+	pmModel *modelEXT;
+	if (!source->moments) {
+	    modelEXT = wildGuess(source, psf);
+	} else {
+	    // use the source moments, etc to guess basic model parameters
+	    modelEXT = pmSourceModelGuess (source, psf->type);
+	    if (!modelEXT) {
+		modelEXT = wildGuess(source, psf);
+	    }
+	    // these valuse are set in pmSourceModelGuess, should this rule be in there as well?
+	    if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+		modelEXT->params->data.F32[PM_PAR_XPOS] = source->moments->x;
+		modelEXT->params->data.F32[PM_PAR_YPOS] = source->moments->y;
+	    } else {
+		modelEXT->params->data.F32[PM_PAR_XPOS] = source->peak->xf;
+		modelEXT->params->data.F32[PM_PAR_YPOS] = source->peak->yf;
+	    }
+	}
+
+	// set PSF parameters for this model (apply 2D shape model)
+	pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+	if (modelPSF == NULL) {
+	    psError(PSPHOT_ERR_PSF, false,
+		    "Failed to determine PSF model at r,c = (%d,%d); trying centre of image",
+		    source->peak->y, source->peak->x);
+	    //
+	    // Try the centre of the image
+	    //
+	    modelEXT->params->data.F32[PM_PAR_XPOS] = 0.5*readout->image->numCols;
+	    modelEXT->params->data.F32[PM_PAR_YPOS] = 0.5*readout->image->numRows;
+	    modelPSF = pmModelFromPSF (modelEXT, psf);
+	    if (modelPSF == NULL) {
+		psError(PSPHOT_ERR_PSF, false,
+			"Failed to determine PSF model at centre of image");
+		psFree(modelEXT);
+		return false;
+	    }
+
+	    source->mode |= PM_SOURCE_MODE_BADPSF;
+	}
+	psFree (modelEXT);
+
+	// XXX need to define the guess flux?
+	// set the fit radius based on the object flux limit and the model
+	psphotCheckRadiusPSF (readout, source, modelPSF);
+
+	// set the source PSF model
+	source->modelPSF = modelPSF;
+	source->modelPSF->residuals = psf->residuals;
+
+	pmSourceCacheModel (source, maskVal);
+    }
+    psLogMsg ("psphot.models", 4, "built models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    return true;
+}
+
+// XXX do we always know which model is supposed to be used?
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotImageLoop.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotImageLoop.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotImageLoop.c	(revision 22147)
@@ -0,0 +1,101 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotImageLoop (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);
+    
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // for psphot, we force data to be read at the chip level 
+    while ((chip = pmFPAviewNextChip (view, 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.WEIGHT");
+	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.");
+
+	// 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.WEIGHT");
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+	// re-activate files so they will be closed and freed below
+	pmFPAfileActivate (config->files, true, NULL);
+
+	// there is now only a single chip (multiple readouts?). loop over it and process
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		psLogMsg ("psphot", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+		if (! readout->data_exists) { continue; }
+
+		// run the actual photometry analysis on this chip/cell/readout
+		if (!psphotReadout (config, view)) {
+		    psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+	    }
+	}
+
+	// save output which is saved at the chip level
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed output for Chip in psphot.");
+    }
+    // save output which is saved at the fpa level
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed ouput for FPA in psphot.");
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
+// PSPHOT.MASK
+// PSPHOT.WEIGHT
+// 
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotImageMedian.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotImageMedian.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotImageMedian.c	(revision 22147)
@@ -0,0 +1,392 @@
+# include "psphotInternal.h"
+static int npass = 0;
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotImageMedian (pmConfig *config, const pmFPAview *view, psMaskType maskVal)
+{
+    bool status = true;
+    static char *defaultStatsName = "FITTED_MEAN";
+
+    pmReadout *readout = NULL;
+    pmReadout *background = NULL;
+    pmReadout *backSub = NULL;
+
+    psTimerStart ("psphot");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    // user supplied seed, if available
+    unsigned long seed = psMetadataLookupS32 (&status, recipe, "IMSTATS_SEED");
+    if (!status) {
+        seed = 0;
+    }
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, seed);
+
+    // subtract this amount extra from the sky
+    float SKY_BIAS = psMetadataLookupF32 (&status, recipe, "SKY_BIAS");
+    if (!status) {
+        SKY_BIAS = 0;
+    }
+
+    // supply the sky background statistics options
+    char *statsName = psMetadataLookupStr (&status, recipe, "SKY_STAT");
+    if (statsName == NULL) {
+        statsName = defaultStatsName;
+    }
+    psStatsOptions statsOptionLocation = psStatsOptionFromString(statsName);
+    if (!(statsOptionLocation & (PS_STAT_SAMPLE_MEAN |
+                                 PS_STAT_SAMPLE_MEDIAN |
+                                 PS_STAT_ROBUST_MEDIAN |
+                                 PS_STAT_ROBUST_QUARTILE |
+                                 PS_STAT_CLIPPED_MEAN |
+                                 PS_STAT_FITTED_MEAN |
+                                 PS_STAT_FITTED_MEAN_V2 |
+                                 PS_STAT_FITTED_MEAN_V3 |
+                                 PS_STAT_FITTED_MEAN_V4))) {
+        statsOptionLocation = PS_STAT_FITTED_MEAN;
+    }
+
+    psStatsOptions statsOptionWidth = PS_STAT_NONE;
+    if (statsOptionLocation & (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN)) {
+        statsOptionWidth = PS_STAT_SAMPLE_STDEV;
+    } else if (statsOptionLocation & (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_QUARTILE)) {
+#if 1
+        statsOptionWidth = PS_STAT_ROBUST_STDEV; // not set; => NaN
+#else
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+#endif
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_CLIPPED_MEAN) {
+        statsOptionWidth = PS_STAT_CLIPPED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V2) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V2;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V3) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V3;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V4) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V4;
+    } else {
+        psAbort("Unable to estimate variance of selected statsOptionLocations 0x%x", statsOptionLocation);
+    }
+
+    const psStatsOptions statsOption = statsOptionLocation | statsOptionWidth;
+    psStats *stats = psStatsAlloc (statsOption);
+
+    // set range for old-version of sky statistic
+    if (statsOptionLocation & PS_STAT_ROBUST_QUARTILE) {
+        stats->min = 0.25;
+        stats->max = 0.75;
+    }
+
+    // set user-option for number of pixels per region
+    stats->nSubsample = psMetadataLookupF32 (&status, recipe, "IMSTATS_NPIX");
+    if (!status) {
+        stats->nSubsample = 1000;
+    }
+
+    // optionally set the binsize
+    stats->binsize = psMetadataLookupF32 (&status, recipe, "SKY_HISTOGRAM_BINS");
+    if (status) {
+        stats->options |= PS_STAT_USE_BINSIZE;
+    }
+
+    // optionally set the sigma clipping
+    stats->clipSigma = psMetadataLookupF32 (&status, recipe, "SKY_CLIP_SIGMA");
+    if (!status) {
+        if ((stats->options & PS_STAT_FITTED_MEAN) || (stats->options & PS_STAT_FITTED_MEAN_V2)) {
+            stats->clipSigma = 1.0;
+        } else {
+            stats->clipSigma = 3.0;
+        }
+    }
+
+    // stats is not initialized by psStats???  use this to save the input options
+    // XXX re-work this to use the new psStatsInit function
+    psStats *statsDefaults = psStatsAlloc (statsOption);
+    *statsDefaults = *stats;
+
+    // find the currently selected readout
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    pmFPA *inFPA = file->fpa;
+    readout = pmFPAviewThisReadout (view, inFPA);
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // I have the fine image size, I know the binning factor, determine the ruff image size
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXfine = image->numCols;
+    binning->nYfine = image->numRows;
+    binning->nXbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    binning->nYbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkip(binning, image);
+    status = psMetadataAddPtr(recipe, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING", PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
+    PS_ASSERT (status, false);
+
+    // we save the binning structure for use in psphotMagnitudes
+    pmReadout *model      = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL",       inFPA, binning);
+    pmReadout *modelStdev = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL.STDEV", inFPA, binning);
+
+    psF32 **modelData = model->image->data.F32;
+    psF32 **modelStdevData = modelStdev->image->data.F32;
+
+    // measure clipped median for subimages
+    psRegion ruffRegion = {0,0,0,0};
+    psRegion fineRegion = {0,0,0,0};
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+
+            // convert the ruff grid cell to the equivalent fine grid cell
+            // XXX we need to watch out for row0,col0
+            ruffRegion = psRegionSet (ix, ix + 2, iy, iy + 2);
+            fineRegion = psImageBinningSetFineRegion (binning, ruffRegion);
+            fineRegion = psRegionForImage (image, fineRegion);
+
+            psImage *subset  = psImageSubset (image, fineRegion);
+            if (!subset->numCols || !subset->numRows) {
+                psFree (subset);
+                continue;
+            }
+            psImage *submask = psImageSubset (mask, fineRegion);
+
+            // reset the default values
+            *stats = *statsDefaults;
+
+            // Use the selected background statistic for the first pass
+            // If it fails, fall back on the "ROBUST_MEDIAN" version
+            // If both fail, set the pixel to NAN and (below) interpolate
+            // XXX psImageBackground will probably be renamed psImageStats
+            // XXX don't bother trying if there are no valid pixels...
+
+	    psVector *sample = NULL;
+
+	    // turn on stats tracing in desired cells
+	    # if (0)
+	    psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+	    assert (plots);
+	    
+	    int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+	    assert (status);
+	    int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+	    assert (status);
+
+	    bool gotX = (xPlot < 0) || (xPlot == ix);
+	    bool gotY = (yPlot < 0) || (yPlot == iy);
+
+	    if (gotX && gotY) {
+		psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 6);
+		psTraceSetLevel ("psLib.math.vectorRobustStats", 6);
+	    } else {
+		psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 0);
+		psTraceSetLevel ("psLib.math.vectorRobustStats", 0);
+	    }	    
+	    # endif
+
+            if (psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                if (stats->options & PS_STAT_ROBUST_QUARTILE) {
+                    modelData[iy][ix] = stats->robustMedian;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue(stats, statsOptionLocation);
+                }
+                modelStdevData[iy][ix] = psStatsGetValue(stats, statsOptionWidth);
+
+		// supply sample to plotting routing
+		psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+		psFree (sample);
+            } else {
+                psStatsOptions currentOptions = stats->options;
+                stats->options = PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV;
+                if (!psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                    psLogMsg ("psphot", PS_LOG_WARN, "Failed to estimate background using ROBUST_MEDIAN for "
+                               "(%dx%d, (row0,col0) = (%d,%d)",
+                               subset->numRows, subset->numCols, subset->row0, subset->col0);
+                    modelData[iy][ix] = modelStdevData[iy][ix] = NAN;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue (stats, PS_STAT_ROBUST_MEDIAN);
+                    modelStdevData[iy][ix] = psStatsGetValue(stats, PS_STAT_ROBUST_STDEV);
+
+		    // supply sample to plotting routing
+		    psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+		    psFree (sample);
+                }
+                // drop errors caused by psImageBackground failures
+                // XXX we probably should trap and exit on serious failures
+                psErrorClear();
+                stats->options = currentOptions;
+            }
+            modelData[iy][ix] += SKY_BIAS;
+            psFree (subset);
+            psFree (submask);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backraw.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // patch over bad regions (use average of 8 possible neighbor pixels)
+    // XXX consider testing all pixels against the 8 neighbors and replacing outliers...
+    double Count = 0;                   // number of good pixels
+    double Value = 0;                   // sum of good pixel's value
+    double ValueStdev = 0;              // sum of good pixel's standard deviations
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) {
+                Value += modelData[iy][ix];
+                ValueStdev += modelStdevData[iy][ix];
+                Count++;
+                continue;
+            }
+
+            double value = 0;
+            double count = 0;
+            for (int jy = iy - 1; jy <= iy + 1; jy++) {
+                if (jy <   0) continue;
+                if (jy >= model->image->numRows) continue;
+                for (int jx = ix - 1; jx <= ix + 1; jx++) {
+                    if (!jx && !jy) continue;
+                    if (jx   <   0) continue;
+                    if (jx   >= model->image->numCols) continue;
+                    value += modelData[jy][jx];
+                    count += 1.0;
+                }
+            }
+            if (count > 0) modelData[iy][ix] = value / count;
+        }
+    }
+    if (Count == 0) {
+        psError (PSPHOT_ERR_DATA, true, "failed to build background image");
+	psFree(stats);
+	psFree(statsDefaults);
+	psFree(binning);
+	psFree(rng);
+        return false;
+    }
+
+    Value /= Count;
+    ValueStdev /= Count;
+
+    // patch over remaining bad regions (use global average)
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) continue;
+            modelData[iy][ix] = Value;
+            modelStdevData[iy][ix] = ValueStdev;
+        }
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build median image: %f sec\n", psTimerMark ("psphot"));
+
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_STDEV", PS_META_REPLACE, "sky stdev", ValueStdev);
+    psLogMsg ("psphot", PS_LOG_INFO, "image sky : mean %f stdev %f", Value, ValueStdev);
+
+    // measure image and background stats and save for later output
+    psStats *statsBck = psStatsAlloc (PS_STAT_SAMPLE_MEAN |
+                                      PS_STAT_SAMPLE_STDEV |
+                                      PS_STAT_MIN |
+                                      PS_STAT_MAX);
+    psImageStats (statsBck, model->image, NULL, 0);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MN",
+                      PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_SIG",
+                      PS_META_REPLACE, "sky model stdev",         statsBck->sampleStdev);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MAX",
+                      PS_META_REPLACE, "sky model maximum value", statsBck->max);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MIN",
+                      PS_META_REPLACE, "sky model minimum value", statsBck->min);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NX",
+                      PS_META_REPLACE, "sky model size (x)",      model->image->numCols);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NY",
+                      PS_META_REPLACE, "sky model size (y)",      model->image->numRows);
+    psLogMsg ("psphot", PS_LOG_INFO, "background sky : min %f mean %f max %f stdev %f",
+              statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
+    psFree (statsBck);
+
+    // select background pixels, from output background file, or create
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (file) {
+        // we are using PSPHOT.BACKGND as an I/O file: select readout or create
+        if (file->mode == PM_FPA_MODE_INTERNAL) {
+            background = file->readout;
+        } else {
+            background = pmFPAviewThisReadout (view, file->fpa);
+        }
+        if (background == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            background = pmFPAviewThisReadout (view, file->fpa);
+            if ((image->numCols != background->image->numCols) || (image->numRows != background->image->numRows)) {
+                psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for background dimensions");
+                return false;
+            }
+        }
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", image->numCols, image->numRows, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    if (!psImageUnbin (background->image, model->image, binning)) {
+        psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for unbinning");
+        return false;
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build resampled image: %f sec\n", psTimerMark ("psphot"));
+
+    // back-sub image pixels, from output background file (don't create if not requested)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
+    if (file) {
+        // we are using PSPHOT.BACKSUB as an I/O file: select readout or create
+        backSub = pmFPAviewThisReadout (view, file->fpa);
+        if (backSub == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            backSub = pmFPAviewThisReadout (view, file->fpa);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "image.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+        sprintf (name, "back.%02d.fits", npass);
+        psphotSaveImage (NULL, background->image, name);
+        sprintf (name, "mask.%02d.fits", npass);
+        psphotSaveImage (NULL, mask, name);
+        sprintf (name, "backmdl.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // subtract the background model (save in backSub, if requested)
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+            image->data.F32[j][i] -= backData[j][i];
+            if (backSub) {
+                backSub->image->data.F32[j][i] = image->data.F32[j][i];
+            }
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backsub.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+    }
+    npass ++;
+
+    psLogMsg ("psphot", PS_LOG_INFO, "subtracted background model: %f sec\n", psTimerMark ("psphot"));
+    psFree(stats);
+    psFree(statsDefaults);
+    psFree(binning);
+    psFree(rng);
+
+    // the pmReadout selected in this function are all view on entries in config->files
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotInternal.h
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotInternal.h	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotInternal.h	(revision 22147)
@@ -0,0 +1,17 @@
+/* this file is included by the internal, library functions. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef PSPHOT_INTERNAL_H
+#define PSPHOT_INTERNAL_H
+
+#include <stdio.h>
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+#endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotIsophotal.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotIsophotal.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotIsophotal.c	(revision 22147)
@@ -0,0 +1,60 @@
+# include "psphot.h"
+
+bool psphotIsophotal (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  bool status;
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *flux = source->extpars->profile->flux;
+
+  // flux at which to measure isophotal parameters
+  // XXX cache this?
+  float ISOPHOT_FLUX = psMetadataLookupF32 (&status, recipe, "ISOPHOTAL_FLUX");
+
+  // find the first bin below the flux level and the last above the level
+  // XXX can this be done faster with disection?
+  // XXX do I need to worry about crazy outliers?  
+  // XXX should i be smoothing or fitting the curve?
+  int firstBelow = -1;
+  int lastAbove = -1;
+  for (int i = 0; i < flux->n; i++) {
+    if (flux->data.F32[i] > ISOPHOT_FLUX) lastAbove = i;
+    if ((firstBelow < 0) && (flux->data.F32[i] < ISOPHOT_FLUX)) firstBelow = i;
+  }
+
+  // need to examine pixels in this vicinity
+  float isophotalFluxFirst = 0;
+  float isophotalFluxLast = 0;
+  for (int i = 0; i <= PS_MAX(firstBelow, lastAbove); i++) {
+    if (i <= firstBelow) {
+      isophotalFluxFirst += flux->data.F32[i];
+    }
+    if (i <= lastAbove) {
+      isophotalFluxLast += flux->data.F32[i];
+    }
+  }
+  float isophotalFlux    = 0.5*(isophotalFluxLast + isophotalFluxFirst);
+  float isophotalFluxErr = 0.5*fabs(isophotalFluxLast - isophotalFluxFirst);
+
+  float isophotalRad     = 0.5*(radius->data.F32[firstBelow] + radius->data.F32[lastAbove]);
+  float isophotalRadErr  = 0.5*fabs(radius->data.F32[firstBelow] - radius->data.F32[lastAbove]);
+
+  if (!source->extpars->isophot) {
+    source->extpars->isophot = pmSourceIsophotalValuesAlloc ();
+  }
+  
+  // these are uncalibrated: instrumental mags and pixel units
+  source->extpars->isophot->mag    = -2.5*log10(isophotalFlux);
+  source->extpars->isophot->magErr = isophotalFluxErr / isophotalFlux;
+
+  source->extpars->isophot->rad    = isophotalRad;
+  source->extpars->isophot->radErr = isophotalRadErr;
+
+  return true;
+
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotKernelFromPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotKernelFromPSF.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotKernelFromPSF.c	(revision 22147)
@@ -0,0 +1,20 @@
+# include "psphot.h"
+
+psKernel *psphotKernelFromPSF (pmSource *source, int nPix) {
+
+  assert (source);
+  assert (source->psfFlux); // XXX build if needed?
+
+  int x0 = source->peak->xf - source->psfFlux->col0;
+  int y0 = source->peak->yf - source->psfFlux->row0;
+
+  // need to decide on the size: dynamically? statically?
+  psKernel *psf = psKernelAlloc (-nPix, +nPix, -nPix, +nPix);
+
+  for (int j = psf->yMin; j <= psf->yMax; j++) {
+    for (int i = psf->xMin; i <= psf->xMax; i++) {
+      psf->kernel[j][i] = source->psfFlux->data.F32[y0 + j][x0 + i];
+    }
+  }
+  return psf;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotKron.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotKron.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotKron.c	(revision 22147)
@@ -0,0 +1,8 @@
+# include "psphot.h"
+
+bool psphotKron (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  psLogMsg ("psphot", PS_LOG_INFO, "not implemented\n");
+  return true;
+
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotLoadPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotLoadPSF.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotLoadPSF.c	(revision 22147)
@@ -0,0 +1,24 @@
+# include "psphotInternal.h"
+
+// load an externally supplied psf model
+pmPSF *psphotLoadPSF (pmConfig *config, const pmFPAview *view, psMetadata *recipe) {
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.PSF.LOAD");
+    if (!readout) return NULL;
+
+    // check if a PSF model is supplied by the user
+    pmPSF *psf = psMetadataLookupPtr (NULL, readout->parent->parent->analysis, "PSPHOT.PSF");
+    if (psf == NULL) {
+        psLogMsg ("psphot", 3, "no psf supplied for this readout");
+        return NULL;
+    }
+
+    if (!psphotPSFstats (readout, recipe, psf)) psAbort("cannot measure PSF shape terms");
+
+    psLogMsg ("psphot", 3, "using externally supplied PSF model for this readout");
+
+    // we return a psf which can be free (and which may be removed from the metadata)
+    psMemIncrRefCounter (psf);
+    return psf;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMagnitudes.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMagnitudes.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMagnitudes.c	(revision 22147)
@@ -0,0 +1,53 @@
+# include "psphotInternal.h"
+
+bool psphotMagnitudes(psArray *sources, psMetadata *recipe, pmPSF *psf, pmReadout *background) {
+
+    bool status = false;
+    int Nap = 0;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType mark = psMetadataLookupU8(&status, recipe, "MASK.MARK"); // Mask value for bad pixels
+    assert (mark);
+
+    pmSourceMagnitudesInit (recipe);
+
+    // XXX require that we have a background model, or
+    // allow it to be missing, setting local sky to 0.0?
+    PS_ASSERT (background, false);
+
+    // the binning details are saved on the analysis metadata
+    psImageBinning *binning = psMetadataLookupPtr(&status, recipe, "PSPHOT.BACKGROUND.BINNING");
+    PS_ASSERT (status, false);
+
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_APCORR | PM_SOURCE_PHOT_WEIGHT;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        status = pmSourceMagnitudes (source, psf, photMode, maskVal, mark);
+        if (status) Nap ++;
+
+        source->sky = psImageUnbinPixel(source->peak->x, source->peak->y, background->image, binning);
+        if (isnan(source->sky) && false) {
+          psError(PSPHOT_ERR_SKY, false, "Setting pmSource.sky");
+          psErrorStackPrint(NULL, " ");
+          psErrorClear();
+        }
+    }
+
+    psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "measure magnitudes : %f sec for %ld objects (%d with apertures)\n", psTimerMark ("psphot"), sources->n, Nap);
+    return true;
+}
+
+// XXX add in a measurement of the bright and faint completeness values
+// XXX push these into the recipe
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMakeFluxScale.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMakeFluxScale.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMakeFluxScale.c	(revision 22147)
@@ -0,0 +1,63 @@
+# include "psphotInternal.h"
+
+bool psphotMakeFluxScale (psImage *image, psMetadata *recipe, pmPSF *psf) {
+
+    bool status = false;
+
+    psTimerStart ("residuals");
+
+    int Nx = psMetadataLookupS32(&status, recipe, "PSF.FLUXSCALE.NX");
+    int Ny = psMetadataLookupS32(&status, recipe, "PSF.FLUXSCALE.NY");
+
+    // stats structure for use by ApTrend : XXX make parameters user setable
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+
+    pmTrend2D *trend = pmTrend2DAlloc (PM_TREND_MAP, image, Nx, Ny, stats);
+    
+    psVector *xPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+    psVector *yPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+    psVector *fPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+
+    int Npts = 0;
+
+    // generate a set of test normalized PSF fluxes filling the grid 
+    for (int ix = 0; ix < Nx; ix++) {
+	for (int iy = 0; iy < Ny; iy++) {
+	    
+	    float x = psImageBinningGetFineX (trend->map->binning, ix + 0.5);
+	    float y = psImageBinningGetFineY (trend->map->binning, iy + 0.5);
+
+	    // create normalized model object at xc,yc 
+	    pmModel *model = pmModelFromPSFforXY (psf, x, y, 1.0);
+
+	    // measure the fitMag for this model
+	    float fitSum = model->modelFlux (model->params);
+	    assert (fitSum > 0);
+	    assert (isfinite(fitSum));
+
+	    xPts->data.F32[Npts] = x;
+	    yPts->data.F32[Npts] = y;
+	    fPts->data.F32[Npts] = fitSum;
+	    Npts ++;
+	    assert (Npts <= xPts->nalloc);
+	    psFree (model);
+	}
+    }
+    xPts->n = Npts;
+    yPts->n = Npts;
+    fPts->n = Npts;
+    
+    // XXX test for errors here
+    pmTrend2DFit (trend, NULL, 0xff, xPts, yPts, fPts, NULL);
+    
+    // XXX do something useful to measure residual statistics
+
+    psf->FluxScale = trend;
+
+    psFree (xPts);
+    psFree (yPts);
+    psFree (fPts);
+    psFree (stats);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMakeResiduals.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMakeResiduals.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMakeResiduals.c	(revision 22147)
@@ -0,0 +1,275 @@
+# include "psphotInternal.h"
+
+bool psphotMakeResiduals (psArray *sources, psMetadata *recipe, pmPSF *psf, psMaskType maskVal) {
+
+    bool status, isPSF;
+    double flux, dflux;
+    psU8 mflux;
+
+    psTimerStart ("residuals");
+
+    if (!psMetadataLookupBool(&status, recipe, "PSF.RESIDUALS")) return true;
+
+    int SPATIAL_ORDER = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.SPATIAL_ORDER");
+    PS_ASSERT (status, false);
+    if (SPATIAL_ORDER != 0 && SPATIAL_ORDER != 1) {
+        psError(PSPHOT_ERR_CONFIG, true, "PSF.RESIDUALS.SPATIAL_ORDER must be 0 or 1 (not %d)",
+                SPATIAL_ORDER);
+        return false;
+    }
+
+    int xBin = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.XBIN");
+    PS_ASSERT (status, false);
+    PS_ASSERT (xBin != 0, false);
+
+    int yBin = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.YBIN");
+    PS_ASSERT (status, false);
+    PS_ASSERT (yBin != 0, false);
+
+    float nSigma = psMetadataLookupF32(&status, recipe, "PSF.RESIDUALS.NSIGMA");
+    PS_ASSERT (status, false);
+
+    float pixelSN = psMetadataLookupF32(&status, recipe, "PSF.RESIDUALS.PIX.SN");
+    PS_ASSERT (status, false);
+
+    char *modeString = psMetadataLookupStr(&status, recipe, "PSF.RESIDUALS.INTERPOLATION");
+    PS_ASSERT (status, false);
+
+    psImageInterpolateMode mode = psImageInterpolateModeFromString (modeString);
+    if (mode == PS_INTERPOLATE_NONE) {
+        psError(PSPHOT_ERR_CONFIG, false, "invalid interpolation in psphot.config");
+        return false;
+    }
+
+    char *statString = psMetadataLookupStr(&status, recipe, "PSF.RESIDUALS.STATISTIC");
+    PS_ASSERT (status, false);
+
+    psStatsOptions statOption = psStatsOptionFromString (statString);
+    if (!statOption) {
+        psError(PSPHOT_ERR_CONFIG, false, "invalid residual statistic in psphot.config");
+        return false;
+    }
+
+    // user parameters:
+    // size of aperture (determine from source images?)
+    // binning factor
+
+    // select the subset of sources which are the PSFSTARs
+    // for each input source:
+    // - construct a residual image, renormalized
+    // - construct a renormalized weight image
+    // - construct a new mask image
+
+    // construct the output residual table (Nx*DX,Ny*DY)
+    // for each output pixel:
+    // - construct a histogram of the values & weights (interpolate to the common pixel coordinate)
+    // - measure the robust median & sigma
+    // - reject (mask) input pixels which are outliers
+    // - re-measure the robust median & sigma
+    // - set output pixel, weight, and mask
+
+    const int badMask = 1;              // mask bits
+    const int poorMask = 2;             //       from psImageInterpolate
+    const int clippedMask = 4;          // mask bit set for clipped values
+
+    bool offImage = false;              // pixel is off the image
+
+    // determine the maximum image size from the input sources
+    int xSize = 0;
+    int ySize = 0;
+
+    psVector *xC = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *yC = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    // build (DATA - MODEL) [an image] for each psf star
+    psArray *input = psArrayAllocEmpty (100);
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // which model to use?
+        pmModel *model = pmSourceGetModel (&isPSF, source);
+        if (model == NULL) continue;  // model must be defined
+
+        psImage *image  = psImageCopy (NULL, source->pixels,   PS_TYPE_F32);
+        psImage *weight = psImageCopy (NULL, source->weight,   PS_TYPE_F32);
+        psImage *mask   = psImageCopy (NULL, source->maskView, PS_TYPE_U8);
+        pmModelSub (image, mask, model, PM_MODEL_OP_FUNC, maskVal);
+
+        // re-normalize image and weight
+        float Io = model->params->data.F32[PM_PAR_I0];
+        psBinaryOp (image, image, "/", psScalarAlloc(Io, PS_TYPE_F32));
+        psBinaryOp (weight, weight, "/", psScalarAlloc(Io*Io, PS_TYPE_F32));
+
+        // we will interpolate the image and weight - include the mask or not?
+        // XXX consider better values for the mask bits
+        psImageInterpolateOptions *interp =
+            psImageInterpolateOptionsAlloc(mode, image, weight, NULL, 0xff, 0.0, 0.0, badMask, poorMask, 0.0);
+        psArrayAdd (input,  100, interp);
+
+        // save the X,Y position for future reference
+        xC->data.F32[xC->n] = model->params->data.F32[PM_PAR_XPOS];
+        yC->data.F32[yC->n] = model->params->data.F32[PM_PAR_YPOS];
+        psVectorExtend (xC, 100, 1);
+        psVectorExtend (yC, 100, 1);
+
+        xSize = PS_MAX (xSize, image->numCols);
+        ySize = PS_MAX (ySize, image->numRows);
+
+        // free up the excess references
+        psFree (mask);
+        psFree (image);
+        psFree (weight);
+        psFree (interp);
+    }
+    pmResiduals *resid = pmResidualsAlloc (xSize, ySize, xBin, yBin);
+
+    // x(resid) = (x(image) - Xo)*xBin + xCenter
+
+    psVector *fluxes  = psVectorAlloc (input->n, PS_TYPE_F32);
+    psVector *dfluxes = psVectorAlloc (input->n, PS_TYPE_F32);
+    psVector *fmasks  = psVectorAlloc (input->n, PS_TYPE_U8);
+
+    // statistic to use to determine baseline for clipping
+    psStats *fluxClip     = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psStats *fluxClipDef  = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    // statistic to use to determine output flux
+    // XXX make API to convert statOption for MEAN/MEDIAN in to corresponding STDEV?
+    psStats *fluxStats    = psStatsAlloc (statOption | PS_STAT_SAMPLE_STDEV);
+    psStats *fluxStatsDef = psStatsAlloc (statOption | PS_STAT_SAMPLE_STDEV);
+
+    // Use psF64 to minimize overflow problems?
+    psImage *A = psImageAlloc(3, 3, PS_TYPE_F64); // Least-squares matrix
+    psVector *B = psVectorAlloc(3, PS_TYPE_F64); // Least-squares vector
+
+    // Solve MODEL = R + x R_x + y R_y in pixel-by-pixel a least-squares sense
+    // (If SPATIAL_ORDER == 0, just solve MODEL = R)
+    for (int oy = 0; resid != NULL && oy < resid->Ro->numRows; oy++) {
+        for (int ox = 0; ox < resid->Ro->numCols; ox++) {
+
+            // build the vector of data values for this output pixel
+            for (int i = 0; i < input->n; i++) {
+
+                psImageInterpolateOptions *interp = input->data[i];
+
+                // fractional image position
+                float ix = (ox + 0.5 - resid->xCenter) / (float) xBin + xC->data.F32[i] - interp->image->col0;
+                float iy = (oy + 0.5 - resid->yCenter) / (float) yBin + yC->data.F32[i] - interp->image->row0;
+
+                mflux = 0;
+                offImage = false;
+                if (psImageInterpolate (&flux, &dflux, &mflux, ix, iy, interp) == PS_INTERPOLATE_STATUS_OFF) {
+                    // This pixel is off the image
+                    offImage = true;
+                }
+                fluxes->data.F32[i] = flux;
+                dfluxes->data.F32[i] = dflux;
+                fmasks->data.U8[i] = mflux;
+                // fprintf (stderr, "%f %f : %f %f (%d)\n", ix, iy, flux, dflux, fmasks->data.U8[i]);
+            }
+
+            // measure the robust median to determine a baseline reference value
+            *fluxClip = *fluxClipDef;
+            psVectorStats (fluxClip, fluxes, NULL, fmasks, 0xff);
+            psErrorClear();             // clear (ignore) any outstanding errors
+
+            // mark input pixels which are more than N sigma from the median
+            for (int i = 0; i < fluxes->n; i++) {
+                float delta = fluxes->data.F32[i] - fluxClip->robustMedian;
+                float sigma = sqrt (dfluxes->data.F32[i]);
+                float swing = fabs(delta) / sigma;
+
+                // make this a user option
+                if (swing > nSigma) {
+                    fmasks->data.U8[i] = clippedMask;
+                }
+            }
+
+            if (offImage || SPATIAL_ORDER == 0) {
+                // measure the desired statistic on the unclipped pixels
+                *fluxStats = *fluxStatsDef;
+                psVectorStats (fluxStats, fluxes, NULL, fmasks, 0xff);
+                psErrorClear();         // clear (ignore) any outstanding errors
+
+                resid->Ro->data.F32[oy][ox] = psStatsGetValue(fluxStats, statOption);
+                resid->Rx->data.F32[oy][ox] = resid->Ry->data.F32[oy][ox] = 0.0;
+                //resid->weight->data.F32[oy][ox] = fluxStats->sampleStdev;
+
+		if (resid->Ro->data.F32[oy][ox] < pixelSN*fluxStats->sampleStdev) {
+		  resid->mask->data.U8[oy][ox] = 1;
+		}
+
+            } else {
+                assert (SPATIAL_ORDER == 1);
+                psImageInit(A, 0.0);
+                psVectorInit(B, 0.0);
+                for (int i = 0; i < fluxes->n; i++) {
+                    if (fmasks->data.U8[i]) continue;
+                    B->data.F64[0] += fluxes->data.F32[i]/dfluxes->data.F32[i];
+                    B->data.F64[1] += fluxes->data.F32[i]*xC->data.F32[i]/dfluxes->data.F32[i];
+                    B->data.F64[2] += fluxes->data.F32[i]*yC->data.F32[i]/dfluxes->data.F32[i];
+
+                    A->data.F64[0][0] += 1.0/dfluxes->data.F32[i];
+                    A->data.F64[1][0] += xC->data.F32[i]/dfluxes->data.F32[i];
+                    A->data.F64[2][0] += yC->data.F32[i]/dfluxes->data.F32[i];
+
+                    A->data.F64[1][1] += PS_SQR(xC->data.F32[i])/dfluxes->data.F32[i];
+                    A->data.F64[2][2] += PS_SQR(yC->data.F32[i])/dfluxes->data.F32[i];
+                    A->data.F64[1][2] += xC->data.F32[i]*yC->data.F32[i]/dfluxes->data.F32[i];
+                }
+
+                A->data.F64[0][1] = A->data.F64[1][0];
+                A->data.F64[0][2] = A->data.F64[2][0];
+                A->data.F64[2][1] = A->data.F64[1][2];
+
+                if (!psMatrixGJSolve(A, B)) {
+                    psError(PSPHOT_ERR_PSF, false, "Singular matrix solving for (y,x) = (%d,%d)'s residuals",
+                            oy, ox);
+                    psFree(resid); resid = NULL;
+                    break;
+                }
+
+                resid->Ro->data.F32[oy][ox] = B->data.F64[0];
+                resid->Rx->data.F32[oy][ox] = B->data.F64[1];
+                resid->Ry->data.F32[oy][ox] = B->data.F64[2];
+
+		float dRo = sqrt(A->data.F32[0][0]);
+		if (resid->Ro->data.F32[oy][ox] < pixelSN*dRo) {
+		  resid->mask->data.U8[oy][ox] = 1;
+		}
+                //resid->weight->data.F32[oy][ox] = XXX;
+            }
+        }
+    }
+
+    psFree (A);
+    psFree (B);
+
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "generate residuals for %ld objects: %f sec\n", input->n, psTimerMark ("residuals"));
+
+    psFree (xC);
+    psFree (yC);
+    psFree (input);
+
+    psFree (fluxes);
+    psFree (dfluxes);
+    psFree (fmasks);
+
+    psFree (fluxStats);
+    psFree (fluxStatsDef);
+    psFree (fluxClip);
+    psFree (fluxClipDef);
+
+    if (resid != NULL && psTraceGetLevel("psphot") > 5) {
+      psphotSaveImage (NULL, resid->Ro,     "resid.ro.fits");
+      psphotSaveImage (NULL, resid->Rx,     "resid.rx.fits");
+      psphotSaveImage (NULL, resid->Ry,     "resid.ry.fits");
+      psphotSaveImage (NULL, resid->weight, "resid.wt.fits");
+      psphotSaveImage (NULL, resid->mask,   "resid.mk.fits");
+    }
+
+    psf->residuals = resid;
+    return (resid != NULL) ? true : false;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMaskReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMaskReadout.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMaskReadout.c	(revision 22147)
@@ -0,0 +1,71 @@
+# include "psphotInternal.h"
+
+// generate mask and weight if not defined, additional mask for restricted subregion 
+bool psphotSetMaskAndWeight (pmConfig *config, pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+
+    // ** Interpret the mask values:
+
+    // single-bit named masks
+    psMaskType maskMark = pmConfigMask("MARK",     config); // Mask value for marking
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.MARK", PS_META_REPLACE, "user-defined mask", maskMark);
+
+    psMaskType maskSat  = pmConfigMask("SAT", 	   config); // Mask value for saturated pixels
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.SAT", PS_META_REPLACE, "user-defined mask", maskSat);
+
+    psMaskType maskBad  = pmConfigMask("BAD", 	   config); // Mask value for bad pixels
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.BAD", PS_META_REPLACE, "user-defined mask", maskBad);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "PSPHOT"); // Mask value for bad pixels
+    if (!maskVal) {
+      const char *maskValStr = psMetadataLookupStr(NULL, recipe, "MASKVAL"); // String with mask names
+      if (!maskValStr) {
+        psError(PSPHOT_ERR_CONFIG, false, "Missing recipe item: MASKVAL(STR)");
+        return false;
+      }
+      maskVal = pmConfigMask(maskValStr, config); // Mask values to mask against
+      psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", maskVal);
+      assert (maskVal);
+    }
+
+    // generate mask & weight images if they don't already exit
+    if (!readout->mask) {
+        if (!pmReadoutGenerateMask(readout, maskSat, maskBad)) {
+            psError (PSPHOT_ERR_CONFIG, false, "trouble creating mask");
+            return false;
+        }
+    }
+    if (!readout->weight) {
+        if (!pmReadoutGenerateWeight(readout, true)) {
+            psError (PSPHOT_ERR_CONFIG, false, "trouble creating weight");
+            return false;
+        }
+    }
+
+    // mask the excluded outer pixels
+    // these coordinates refer to the parent image
+    // these bounds will saturate on the subimage
+    // negative upper bounds will subtract from the *subimage*
+    float XMIN  = psMetadataLookupF32 (&status, recipe, "XMIN");
+    float XMAX  = psMetadataLookupF32 (&status, recipe, "XMAX");
+    float YMIN  = psMetadataLookupF32 (&status, recipe, "YMIN");
+    float YMAX  = psMetadataLookupF32 (&status, recipe, "YMAX");
+    psRegion valid = psRegionSet (XMIN, XMAX, YMIN, YMAX);
+
+    // restrict the supplied region above to the valid area on the image
+    psRegion keep = psRegionForImage (readout->image, valid);
+
+    // psImageKeepRegion assumes the region refers to the parent coordinates
+    psImageKeepRegion (readout->mask, keep, "OR", maskBad);
+
+    // test output of files at this stage
+    if (psTraceGetLevel("psphot") >= 5) {
+        psphotSaveImage (NULL, readout->image,  "image.fits");
+        psphotSaveImage (NULL, readout->mask,   "mask.fits");
+        psphotSaveImage (NULL, readout->weight, "weight.fits");
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMergeSources.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMergeSources.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMergeSources.c	(revision 22147)
@@ -0,0 +1,52 @@
+# include "psphotInternal.h"
+
+// add newly selected sources to the existing list of sources
+bool psphotMergeSources (psArray *oldSources, psArray *newSources) {
+
+    for (int i = 0; i < newSources->n; i++) {
+        pmSource *source = newSources->data[i];
+        psArrayAdd (oldSources, 100, source);
+    }
+    return true;
+}
+
+// merge the externally supplied sources with the existing sources.  mark them as having
+// mode PM_SOURCE_MODE_EXTERNAL
+bool psphotLoadExtSources (pmConfig *config, const pmFPAview *view, psArray *sources) {
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CMF");
+    if (!readout) {
+        psLogMsg ("psphot", 3, "no external sources supplied");
+        return true;
+    }
+
+    psArray *extSources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (!extSources) {
+        psLogMsg ("psphot", 3, "no external sources for this readout");
+        return true;
+    }
+
+    for (int i = 0; i < extSources->n; i++) {
+        pmSource *source = extSources->data[i];
+        source->mode |= PM_SOURCE_MODE_EXTERNAL;
+        pmModel *model = source->modelPSF;
+
+        float xpos = model->params->data.F32[PM_PAR_XPOS];
+        float ypos = model->params->data.F32[PM_PAR_YPOS];
+
+        source->peak = pmPeakAlloc(xpos, ypos, 1.0, PM_PEAK_LONE);
+        source->peak->xf = xpos;
+        source->peak->yf = ypos;
+        source->peak->flux = 1.0;
+
+        // drop the loaded source modelPSF
+        psFree (source->modelPSF);
+        source->modelPSF = NULL;
+    }
+
+    psphotMergeSources (sources, extSources);
+    psLogMsg ("psphot", 3, "%ld external sources merged to yield %ld total sources", extSources->n, sources->n);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelBackground.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelBackground.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelBackground.c	(revision 22147)
@@ -0,0 +1,319 @@
+# include "psphotInternal.h"
+static int npass = 0;
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotModelBackground (pmConfig *config, const pmFPAview *view, const char *filename)
+{
+    bool status = true;
+    static char *defaultStatsName = "FITTED_MEAN";
+
+    psTimerStart ("psphot");
+
+    // find the currently selected readout
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, filename);
+    pmFPA *inFPA = file->fpa;
+    pmReadout *readout = pmFPAviewThisReadout (view, inFPA);
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // user supplied seed, if available
+    unsigned long seed = psMetadataLookupS32 (&status, recipe, "IMSTATS_SEED");
+    if (!status) {
+        seed = 0;
+    }
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, seed);
+
+    // subtract this amount extra from the sky
+    float SKY_BIAS = psMetadataLookupF32 (&status, recipe, "SKY_BIAS");
+    if (!status) {
+        SKY_BIAS = 0;
+    }
+
+    // supply the sky background statistics options
+    char *statsName = psMetadataLookupStr (&status, recipe, "SKY_STAT");
+    if (statsName == NULL) {
+        statsName = defaultStatsName;
+    }
+    psStatsOptions statsOptionLocation = psStatsOptionFromString(statsName);
+    if (!(statsOptionLocation & (PS_STAT_SAMPLE_MEAN |
+                                 PS_STAT_SAMPLE_MEDIAN |
+                                 PS_STAT_ROBUST_MEDIAN |
+                                 PS_STAT_ROBUST_QUARTILE |
+                                 PS_STAT_CLIPPED_MEAN |
+                                 PS_STAT_FITTED_MEAN |
+                                 PS_STAT_FITTED_MEAN_V2 |
+                                 PS_STAT_FITTED_MEAN_V3 |
+                                 PS_STAT_FITTED_MEAN_V4))) {
+        statsOptionLocation = PS_STAT_FITTED_MEAN;
+    }
+
+    psStatsOptions statsOptionWidth = PS_STAT_NONE;
+    if (statsOptionLocation & (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN)) {
+        statsOptionWidth = PS_STAT_SAMPLE_STDEV;
+    } else if (statsOptionLocation & (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_QUARTILE)) {
+#if 1
+        statsOptionWidth = PS_STAT_ROBUST_STDEV; // not set; => NaN
+#else
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+#endif
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_CLIPPED_MEAN) {
+        statsOptionWidth = PS_STAT_CLIPPED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V2) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V2;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V3) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V3;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V4) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V4;
+    } else {
+        psAbort("Unable to estimate variance of selected statsOptionLocations 0x%x", statsOptionLocation);
+    }
+
+    const psStatsOptions statsOption = statsOptionLocation | statsOptionWidth;
+    psStats *stats = psStatsAlloc (statsOption);
+
+    // set range for old-version of sky statistic
+    if (statsOptionLocation & PS_STAT_ROBUST_QUARTILE) {
+        stats->min = 0.25;
+        stats->max = 0.75;
+    }
+
+    // set user-option for number of pixels per region
+    stats->nSubsample = psMetadataLookupF32 (&status, recipe, "IMSTATS_NPIX");
+    if (!status) {
+        stats->nSubsample = 1000;
+    }
+
+    // optionally set the binsize
+    stats->binsize = psMetadataLookupF32 (&status, recipe, "SKY_HISTOGRAM_BINS");
+    if (status) {
+        stats->options |= PS_STAT_USE_BINSIZE;
+    }
+
+    // optionally set the sigma clipping
+    stats->clipSigma = psMetadataLookupF32 (&status, recipe, "SKY_CLIP_SIGMA");
+    if (!status) {
+        if ((stats->options & PS_STAT_FITTED_MEAN) || (stats->options & PS_STAT_FITTED_MEAN_V2)) {
+            stats->clipSigma = 1.0;
+        } else {
+            stats->clipSigma = 3.0;
+        }
+    }
+
+    // stats is not initialized by psStats???  use this to save the input options
+    // XXX re-work this to use the new psStatsInit function
+    psStats *statsDefaults = psStatsAlloc (statsOption);
+    *statsDefaults = *stats;
+
+    // I have the fine image size, I know the binning factor, determine the ruff image size
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXfine = image->numCols;
+    binning->nYfine = image->numRows;
+    binning->nXbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    binning->nYbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkip(binning, image);
+    status = psMetadataAddPtr(recipe, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING", PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
+    PS_ASSERT (status, false);
+
+    // we save the binning structure for use in psphotMagnitudes
+    pmReadout *model      = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL",       inFPA, binning);
+    pmReadout *modelStdev = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL.STDEV", inFPA, binning);
+
+    psF32 **modelData = model->image->data.F32;
+    psF32 **modelStdevData = modelStdev->image->data.F32;
+
+    // measure clipped median for subimages
+    psRegion ruffRegion = {0,0,0,0};
+    psRegion fineRegion = {0,0,0,0};
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+
+            // convert the ruff grid cell to the equivalent fine grid cell
+            // XXX we need to watch out for row0,col0
+            ruffRegion = psRegionSet (ix, ix + 2, iy, iy + 2);
+            fineRegion = psImageBinningSetFineRegion (binning, ruffRegion);
+            fineRegion = psRegionForImage (image, fineRegion);
+
+            psImage *subset  = psImageSubset (image, fineRegion);
+            if (!subset->numCols || !subset->numRows) {
+                psFree (subset);
+                continue;
+            }
+            psImage *submask = psImageSubset (mask, fineRegion);
+
+            // reset the default values
+            *stats = *statsDefaults;
+
+            // Use the selected background statistic for the first pass
+            // If it fails, fall back on the "ROBUST_MEDIAN" version
+            // If both fail, set the pixel to NAN and (below) interpolate
+            // XXX psImageBackground will probably be renamed psImageStats
+            // XXX don't bother trying if there are no valid pixels...
+
+	    psVector *sample = NULL;
+
+	    // turn on stats tracing in desired cells
+	    # if (0)
+	    psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+	    assert (plots);
+	    
+	    int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+	    assert (status);
+	    int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+	    assert (status);
+
+	    bool gotX = (xPlot < 0) || (xPlot == ix);
+	    bool gotY = (yPlot < 0) || (yPlot == iy);
+
+	    if (gotX && gotY) {
+		psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 6);
+		psTraceSetLevel ("psLib.math.vectorRobustStats", 6);
+	    } else {
+		psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 0);
+		psTraceSetLevel ("psLib.math.vectorRobustStats", 0);
+	    }	    
+	    # endif
+
+            if (psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                if (stats->options & PS_STAT_ROBUST_QUARTILE) {
+                    modelData[iy][ix] = stats->robustMedian;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue(stats, statsOptionLocation);
+                }
+                modelStdevData[iy][ix] = psStatsGetValue(stats, statsOptionWidth);
+
+		// supply sample to plotting routing
+		psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+		psFree (sample);
+            } else {
+                psStatsOptions currentOptions = stats->options;
+                stats->options = PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV;
+                if (!psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                    psLogMsg ("psphot", PS_LOG_WARN, "Failed to estimate background using ROBUST_MEDIAN for "
+                               "(%dx%d, (row0,col0) = (%d,%d)",
+                               subset->numRows, subset->numCols, subset->row0, subset->col0);
+                    modelData[iy][ix] = modelStdevData[iy][ix] = NAN;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue (stats, PS_STAT_ROBUST_MEDIAN);
+                    modelStdevData[iy][ix] = psStatsGetValue(stats, PS_STAT_ROBUST_STDEV);
+
+		    // supply sample to plotting routing
+		    psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+		    psFree (sample);
+                }
+                // drop errors caused by psImageBackground failures
+                // XXX we probably should trap and exit on serious failures
+                psErrorClear();
+                stats->options = currentOptions;
+            }
+            modelData[iy][ix] += SKY_BIAS;
+            psFree (subset);
+            psFree (submask);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backraw.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // patch over bad regions (use average of 8 possible neighbor pixels)
+    // XXX consider testing all pixels against the 8 neighbors and replacing outliers...
+    double Count = 0;                   // number of good pixels
+    double Value = 0;                   // sum of good pixel's value
+    double ValueStdev = 0;              // sum of good pixel's standard deviations
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) {
+                Value += modelData[iy][ix];
+                ValueStdev += modelStdevData[iy][ix];
+                Count++;
+                continue;
+            }
+
+            double value = 0;
+            double count = 0;
+            for (int jy = iy - 1; jy <= iy + 1; jy++) {
+                if (jy <   0) continue;
+                if (jy >= model->image->numRows) continue;
+                for (int jx = ix - 1; jx <= ix + 1; jx++) {
+                    if (!jx && !jy) continue;
+                    if (jx   <   0) continue;
+                    if (jx   >= model->image->numCols) continue;
+                    value += modelData[jy][jx];
+                    count += 1.0;
+                }
+            }
+            if (count > 0) modelData[iy][ix] = value / count;
+        }
+    }
+    if (Count == 0) {
+        psError (PSPHOT_ERR_DATA, true, "failed to build background image");
+	psFree(stats);
+	psFree(statsDefaults);
+	psFree(binning);
+	psFree(rng);
+        return false;
+    }
+
+    Value /= Count;
+    ValueStdev /= Count;
+
+    // patch over remaining bad regions (use global average)
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) continue;
+            modelData[iy][ix] = Value;
+            modelStdevData[iy][ix] = ValueStdev;
+        }
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build median image: %f sec\n", psTimerMark ("psphot"));
+
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_STDEV", PS_META_REPLACE, "sky stdev", ValueStdev);
+    psLogMsg ("psphot", PS_LOG_INFO, "image sky : mean %f stdev %f", Value, ValueStdev);
+
+    // measure image and background stats and save for later output
+    psStats *statsBck = psStatsAlloc (PS_STAT_SAMPLE_MEAN |
+                                      PS_STAT_SAMPLE_STDEV |
+                                      PS_STAT_MIN |
+                                      PS_STAT_MAX);
+    psImageStats (statsBck, model->image, NULL, 0);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MN",
+                      PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_SIG",
+                      PS_META_REPLACE, "sky model stdev",         statsBck->sampleStdev);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MAX",
+                      PS_META_REPLACE, "sky model maximum value", statsBck->max);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MIN",
+                      PS_META_REPLACE, "sky model minimum value", statsBck->min);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NX",
+                      PS_META_REPLACE, "sky model size (x)",      model->image->numCols);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NY",
+                      PS_META_REPLACE, "sky model size (y)",      model->image->numRows);
+    psLogMsg ("psphot", PS_LOG_INFO, "background sky : min %f mean %f max %f stdev %f",
+              statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
+
+    psFree(stats);
+    psFree(statsBck);
+    psFree(statsDefaults);
+    psFree(binning);
+    psFree(rng);
+
+    npass ++;
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelGroupInit.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelGroupInit.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelGroupInit.c	(revision 22147)
@@ -0,0 +1,25 @@
+# include "psphotInternal.h"
+
+// Add locally-defined models here.  As these mature, they can be moved to 
+// psModule/src/objects/models
+
+# include "models/pmModel_TEST1.c"
+# include "models/pmModel_STRAIL.c"
+
+static pmModelClass userModels[] = {
+    {"PS_MODEL_TEST1", 7, pmModelFunc_TEST1,  pmModelFlux_TEST1,  pmModelRadius_TEST1,  pmModelLimits_TEST1,  pmModelGuess_TEST1, pmModelFromPSF_TEST1, pmModelParamsFromPSF_TEST1, pmModelFitStatus_TEST1},
+    {"PS_MODEL_STRAIL", 9, pmModelFunc_STRAIL,  pmModelFlux_STRAIL,  pmModelRadius_STRAIL,  pmModelLimits_STRAIL,  pmModelGuess_STRAIL, pmModelFromPSF_STRAIL, pmModelParamsFromPSF_STRAIL, pmModelFitStatus_STRAIL},
+};
+
+void psphotModelClassInit (void) 
+{ 
+
+    // if pmModelClassInit returns false, we have already init'ed
+    if (!pmModelClassInit ()) return;
+
+    int Nmodels = sizeof (userModels) / sizeof (pmModelClass);
+    for (int i = 0; i < Nmodels; i++) {
+	pmModelClassAdd (&userModels[i]);
+    }
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelTest.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelTest.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelTest.c	(revision 22147)
@@ -0,0 +1,241 @@
+# include "psphotInternal.h"
+# define PM_SOURCE_FIT_PSF_X_EXT PM_SOURCE_FIT_PSF_AND_SKY
+
+// XXX add more test information?
+bool psphotModelTest (pmConfig *config, const pmFPAview *view, psMetadata *recipe) {
+
+    bool status;
+    int modelType = -1;
+    float obsMag, fitMag, value;
+    char name[64];
+    pmPSF *psf = NULL;
+    pmSourceFitMode fitMode;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    psMaskType mark    = psMetadataLookupU8(&status, recipe, "MASK.MARK"); // Mask value for bad pixels
+    assert (maskVal);
+    assert (mark);
+
+    // run model fitting tests on a single source?
+    if (!psMetadataLookupBool (&status, recipe, "TEST_FIT")) return false;
+
+    psTimerStart ("modelTest");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, false);
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) POISSON_ERRORS = true;
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // find the various fitting parameters (try test values first)
+    float INNER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_INNER_RADIUS");
+    if (!status || !isfinite(INNER)) {
+        INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    }
+    float OUTER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_OUTER_RADIUS");
+    if (!status || !isfinite(OUTER)) {
+        OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    }
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "TEST_FIT_RADIUS");
+    if (!status || !isfinite(RADIUS)) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    }
+    float mRADIUS = psMetadataLookupF32 (&status, recipe, "TEST_MOMENTS_RADIUS");
+    if (!status || !isfinite(mRADIUS)) {
+        mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // define the source of interest
+    float xObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_X");
+    float yObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_Y");
+    if (!isfinite(xObj) || !isfinite(yObj)) psAbort ("object position is not defined");
+
+    // what fitting mode to use?
+    fitMode = PM_SOURCE_FIT_EXT;
+    char *fitModeWord = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODE");
+    if (fitModeWord && !strcasecmp (fitModeWord, "PSF")) fitMode = PM_SOURCE_FIT_PSF;
+    if (fitModeWord && !strcasecmp (fitModeWord, "CONV")) fitMode = PM_SOURCE_FIT_PSF_X_EXT;
+    if (fitModeWord && !strcasecmp (fitModeWord, "DEFAULT")) fitMode = PM_SOURCE_FIT_EXT;
+
+    // construct the source structures
+    pmSource *source = pmSourceAlloc();
+    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
+    pmSourceDefinePixels (source, readout, xObj, yObj, OUTER);
+
+    // in fitMode, psf sets the model type
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+        psf = psphotLoadPSF (config, view, recipe);
+        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
+        modelType = psf->type;
+        source->type = PM_SOURCE_TYPE_STAR;
+    }
+    if (fitMode == PM_SOURCE_FIT_EXT) {
+        // find the model: supplied by user or first in the PSF_MODEL list
+        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+        if (!status || !strcasecmp (modelName, "DEFAULT")) {
+            // get the list pointers for the PSF_MODEL entries
+
+            psList *list = NULL;
+            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+            if (mdi->type == PS_DATA_STRING) {
+                list = psListAlloc(NULL);
+                psListAdd (list, PS_LIST_HEAD, mdi);
+            } else {
+                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+                list = psMemIncrRefCounter(mdi->data.list);
+            }
+
+            // take the first list element
+            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
+            modelName = item->data.V;
+        }
+        modelType = pmModelClassGetType (modelName);
+        if (modelType < 0) psAbort("unknown model %s", modelName);
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+    }
+    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
+        // we need to load BOTH a psf and an ext model
+        psf = psphotLoadPSF (config, view, recipe);
+        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
+
+        // find the model: supplied by user or first in the PSF_MODEL list
+        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+        if (!status || !strcasecmp (modelName, "DEFAULT")) {
+            // get the list pointers for the PSF_MODEL entries
+
+            psList *list = NULL;
+            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+            if (mdi->type == PS_DATA_STRING) {
+                list = psListAlloc(NULL);
+                psListAdd (list, PS_LIST_HEAD, mdi);
+            } else {
+                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+                list = psMemIncrRefCounter(mdi->data.list);
+            }
+
+            // take the first list element
+            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
+            modelName = item->data.V;
+        }
+        modelType = pmModelClassGetType (modelName);
+        if (modelType < 0) psAbort("unknown model %s", modelName);
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+    }
+
+    // find the local sky
+    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, mark);
+    if (!status) psAbort("pmSourceLocalSky error");
+
+    // get the source moments
+    status = pmSourceMoments (source, mRADIUS);
+    if (!status) psAbort("psSourceMoments error");
+    source->peak->value = source->moments->Peak;
+
+    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->x, source->moments->y);
+    fprintf (stderr, "moments: %f, %f - %f\n", source->moments->Sx, source->moments->Sy, source->moments->Sxy);
+
+    psEllipseMoments moments;
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    psEllipseAxes axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    fprintf (stderr, "axes: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    // get the initial model parameter guess
+    pmModel *model = pmSourceModelGuess (source, modelType);
+    source->modelEXT = model;
+
+    // if any parameters are defined by the user, take those values
+    int nParams = pmModelClassParameterCount (modelType);
+    psF32 *params = model->params->data.F32;
+    params[PM_PAR_XPOS] = xObj; // XXX use the user-supplied value,
+    params[PM_PAR_YPOS] = yObj; // XXX or use the centroid
+    for (int i = 0; i < nParams; i++) {
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+
+        sprintf (name, "TEST_FIT_PAR%d", i);
+        value = psMetadataLookupF32 (&status, recipe, name);
+        if (status && isfinite (value)) {
+            params[i] = value;
+        }
+    }
+
+    float area = params[4]*params[5];
+    fprintf (stderr, "peak: %f @ (%f, %f)\n", source->moments->Sum*area, (double)source->peak->x, (double)source->peak->y);
+
+    // for PSF fitting, set the shape parameters based on the PSF & source position
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+        source->modelPSF = pmModelFromPSF (model, psf);
+        psFree (model);
+        model = source->modelPSF;
+        params = model->params->data.F32;
+    }
+
+    // list model input shape
+    psEllipseShape shape;
+    shape.sx  = 1.4 / model->params->data.F32[4];
+    shape.sy  = 1.4 / model->params->data.F32[5];
+    shape.sxy = model->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    fprintf (stderr, "guess: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    fprintf (stderr, "input parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // define the pixels used for the fit
+    psImageKeepCircle (source->maskObj, xObj, yObj, RADIUS, "OR", mark);
+    psphotSaveImage (NULL, source->maskObj, "mask1.fits");
+
+    char *fitset = psMetadataLookupStr (&status, recipe, "TEST_FIT_SET");
+    if (status) {
+        status = psphotFitSet (source, model, fitset, fitMode, maskVal);
+        exit (0);
+    }
+
+    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
+        // build the psf for the object
+        source->modelPSF = pmModelFromPSF (model, psf);
+        source->modelEXT = model;
+        status = psphotPSFConvModel (source, recipe, maskVal);
+        model = source->modelConv;
+        params = model->params->data.F32;
+    } else {
+        status = pmSourceFitModel (source, model, fitMode, maskVal);
+    }
+
+    // measure the source mags
+    pmSourcePhotometryModel (&fitMag, model);
+    pmSourcePhotometryAper  (&obsMag, model, source->pixels, source->maskObj, maskVal);
+    fprintf (stderr, "ap: %f, fit: %f, apmifit: %f, nIter: %d\n", obsMag, fitMag, obsMag - fitMag, model->nIter);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    // pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+    fprintf (stderr, "output parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // write out
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+
+    psLogMsg ("psphot", PS_LOG_INFO, "model test : %f sec\n", psTimerMark ("modelTest"));
+
+    exit (0);
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelWithPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelWithPSF.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotModelWithPSF.c	(revision 22147)
@@ -0,0 +1,404 @@
+# include "psphot.h"
+
+bool psphotModelWithPSF_LMM (
+    psMinimization *min,
+    psImage *covar,
+    psVector *params,
+    psMinConstraint *constraint,
+    pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func)
+{
+    psTrace("psphot", 3, "---- begin ----\n");
+    PS_ASSERT_PTR_NON_NULL(min, false);
+    PS_ASSERT_VECTOR_NON_NULL(params, false);
+    PS_ASSERT_VECTOR_NON_EMPTY(params, false);
+    PS_ASSERT_VECTOR_TYPE(params, PS_TYPE_F32, false);
+    psVector *paramMask = NULL;
+    if (constraint != NULL) {
+        paramMask = constraint->paramMask;
+        if (paramMask != NULL) {
+            PS_ASSERT_VECTOR_TYPE(paramMask, PS_TYPE_U8, false);
+            PS_ASSERT_VECTORS_SIZE_EQUAL(params, paramMask, false);
+        }
+    }
+    PS_ASSERT_PTR_NON_NULL(func, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    psMinimizeLMLimitFunc checkLimits = NULL;
+    if (constraint) {
+        checkLimits = constraint->checkLimits;
+    }
+
+    // this function has test values and current values for several things
+    // the current value is in lower case
+    // the test value is in upper case
+
+    // allocate internal arrays (current vs Guess)
+    psImage *alpha   = psImageAlloc(params->n, params->n, PS_TYPE_F32);
+    psImage *Alpha   = psImageAlloc(params->n, params->n, PS_TYPE_F32);
+    psVector *beta   = psVectorAlloc(params->n, PS_TYPE_F32);
+    psVector *Beta   = psVectorAlloc(params->n, PS_TYPE_F32);
+    psVector *Params = psVectorAlloc(params->n, PS_TYPE_F32);
+
+    psF32 Chisq = 0.0;
+    psF32 lambda = 0.001;
+    psF32 dLinear = 0.0;
+
+    // generate PCM data storage structure
+    pmPCMData *pcm = pmPCMDataAlloc (params, paramMask, source);
+
+    // calculate initial alpha and beta, set chisq (min->value)
+    min->value = psphotModelWithPSF_SetABX(alpha, beta, params, paramMask, pcm, source, psf, func);
+    if (isnan(min->value)) {
+        min->iter = min->maxIter;
+        return(false);
+    }
+    // dump some useful info if trace is defined
+    if (psTraceGetLevel("psphot") >= 6) {
+        p_psImagePrint(psTraceGetDestination(), alpha, "alpha guess (0)");
+        p_psVectorPrint(psTraceGetDestination(), beta, "beta guess (0)");
+    }
+    if (psTraceGetLevel("psphot") >= 5) {
+        p_psVectorPrint(psTraceGetDestination(), params, "params guess (0)");
+    }
+
+    // iterate until the tolerance is reached, or give up
+    while ((min->iter < min->maxIter) && ((min->lastDelta > min->tol) || !isfinite(min->lastDelta))) {
+        psTrace("psphot", 5, "Iteration number %d.  (max iterations is %d).\n", min->iter, min->maxIter);
+        psTrace("psphot", 5, "Last delta is %f.  Min->tol is %f.\n", min->lastDelta, min->tol);
+
+
+        // set a new guess for Alpha, Beta, Params
+        if (!psMinLM_GuessABP(Alpha, Beta, Params, alpha, beta, params, paramMask, checkLimits, lambda, &dLinear)) {
+            min->iter ++;
+            lambda *= 10.0;
+            continue;
+        }
+
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel("psphot") >= 6) {
+            p_psImagePrint(psTraceGetDestination(), Alpha, "alpha guess (1)");
+            p_psVectorPrint(psTraceGetDestination(), Beta, "beta guess (1)");
+        }
+        if (psTraceGetLevel("psphot") >= 5) {
+            p_psVectorPrint(psTraceGetDestination(), Params, "params guess (1)");
+        }
+
+        // calculate Chisq for new guess, update Alpha & Beta
+        Chisq = psphotModelWithPSF_SetABX(Alpha, Beta, Params, paramMask, pcm, source, psf, func);
+        if (isnan(Chisq)) {
+            min->iter ++;
+            lambda *= 10.0;
+            continue;
+        }
+
+        // convergence criterion:
+        // compare the delta (min->value - Chisq) with the
+        // expected delta from the linear model (dLinear)
+        // accept new guess if it is an improvement (rho > 0), or else increase lambda
+        psF32 rho = (min->value - Chisq) / dLinear;
+
+        psTrace("psphot", 5, "last chisq: %f, new chisq %f, delta: %f, rho: %f\n", min->value,
+                Chisq, min->lastDelta, rho);
+
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel("psphot") >= 6) {
+            p_psImagePrint(psTraceGetDestination(), Alpha, "alpha guess (2)");
+            p_psVectorPrint(psTraceGetDestination(), Beta, "beta guess (2)");
+        }
+
+        /* if (Chisq < min->value) {  */
+        if (rho > 0.0) {
+            min->lastDelta = (min->value - Chisq) / (source->pixels->numCols*source->pixels->numRows - params->n);
+            min->value = Chisq;
+            alpha  = psImageCopy(alpha, Alpha, PS_TYPE_F32);
+            beta   = psVectorCopy(beta, Beta, PS_TYPE_F32);
+            params = psVectorCopy(params, Params, PS_TYPE_F32);
+            lambda *= 0.1;
+
+	    // save the new convolved model image
+	    psFree (source->modelFlux);
+	    source->modelFlux = pmPCMDataSaveImage(pcm);
+        } else {
+            lambda *= 10.0;
+        }
+        min->iter++;
+    }
+    psTrace("psphot", 5, "chisq: %f, last delta: %f, Niter: %d\n", min->value, min->lastDelta, min->iter);
+
+    // construct & return the covariance matrix (if requested)
+    if (covar != NULL) {
+        if (!psMinLM_GuessABP(covar, Beta, Params, alpha, beta, params, paramMask, NULL, 0.0, NULL)) {
+            psTrace ("psphot", 5, "failure to calculate covariance matrix\n");
+        }
+    }
+
+    // free the internal temporary data
+    psFree(alpha);
+    psFree(Alpha);
+    psFree(beta);
+    psFree(Beta);
+    psFree(Params);
+    psFree(pcm);
+
+    if (min->iter == min->maxIter) {
+        psTrace("psphot", 3, "---- end (false) ----\n");
+        return(false);
+    }
+
+    psTrace("psphot", 3, "---- end (true) ----\n");
+    return(true);
+}
+
+psF32 psphotModelWithPSF_SetABX(
+    psImage  *alpha,
+    psVector *beta,
+    const psVector *params,
+    const psVector *paramMask,
+    pmPCMData *pcm,
+    const pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func)
+{
+    // XXX: Check vector sizes.
+    PS_ASSERT_IMAGE_NON_NULL(alpha, NAN);
+    PS_ASSERT_VECTOR_NON_NULL(beta, NAN);
+    PS_ASSERT_VECTOR_NON_NULL(params, NAN);
+
+    PS_ASSERT_PTR_NON_NULL(source, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->pixels, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->weight, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->maskObj, NAN);
+
+    PS_ASSERT_VECTOR_TYPE(params, PS_TYPE_F32, false);
+    if (paramMask) {
+        PS_ASSERT_VECTOR_TYPE(paramMask, PS_TYPE_MASK, false);
+    }
+
+    // generate the model and derivative images for this parameter set
+
+    // storage for model derivatives
+    psVector *deriv = psVectorAlloc(params->n, PS_TYPE_F32);
+
+    // working vector to store local coordinate
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    psImageInit (pcm->model, 0.0);
+    for (int n = 0; n < params->n; n++) {
+	if (!pcm->dmodels->data[n]) continue;
+	psImageInit (pcm->dmodels->data[n], 0.0);
+    }
+
+    // fill in the coordinate and value entries
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+
+	    // XXX can we skip some of the data points where the model
+	    // is not going to be fitted??
+
+            // skip masked points
+	    // XXX probably should not skipped masked points: 
+	    // XXX skip if convolution of unmasked pixels will not see this pixel
+            // if (source->maskObj->data.U8[i][j]) {
+	    // continue;
+	    // }
+
+            // skip zero-weight points
+	    // XXX why is this not masked?
+            // if (source->weight->data.F32[i][j] == 0) {
+	    // continue;
+	    // }
+            // skip nan value points
+	    // XXX why is this not masked?
+            // if (!isfinite(source->pixels->data.F32[i][j])) {
+	    // continue;
+	    // }
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+
+	    pcm->model->data.F32[i][j] = func (deriv, params, coord);
+
+	    for (int n = 0; n < params->n; n++) {
+	      if ((paramMask != NULL) && (paramMask->data.U8[n])) { continue; }
+	      psImage *dmodel = pcm->dmodels->data[n];
+	      dmodel->data.F32[i][j] = deriv->data.F32[n];
+	    }
+        }
+    }
+    psFree(coord);
+    psFree(deriv);
+
+
+    // convolve model and dmodel arrays with PSF
+    psImageConvolveDirect (pcm->modelConv, pcm->model, psf);
+    for (int n = 0; n < pcm->dmodels->n; n++) {
+      if (pcm->dmodels->data[n] == NULL) continue;
+      psImage *dmodel = pcm->dmodels->data[n];
+      psImage *dmodelConv = pcm->dmodelsConv->data[n];
+      psImageConvolveDirect (dmodelConv, dmodel, psf);
+    }
+
+    // XXX TEST : SAVE IMAGES
+# if (1) 
+    psphotSaveImage (NULL, psf->image, "psf.fits");
+    psphotSaveImage (NULL, pcm->model, "model.fits");
+    psphotSaveImage (NULL, pcm->modelConv, "modelConv.fits");
+    psphotSaveImage (NULL, source->pixels, "obj.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+    psphotSaveImage (NULL, source->weight, "weight.fits");
+# endif
+
+    // zero alpha and beta for summing below
+    psImageInit (alpha, 0.0);
+    psVectorInit (beta, 0.0);
+    float chisq = 0.0;
+
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+	  // XXX are we doing the right thing with the mask?
+            // skip masked points
+            if (source->maskObj->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+            // skip nan value points
+            if (!isfinite(source->pixels->data.F32[i][j])) {
+                continue;
+            }
+
+	    float ymodel  = pcm->modelConv->data.F32[i][j];
+	    float yweight = 1.0 / source->weight->data.F32[i][j];
+	    float delta = ymodel - source->pixels->data.F32[i][j];
+
+	    chisq += PS_SQR(delta) * yweight;
+
+	    if (isnan(delta))
+	      psAbort("nan in delta");
+	    if (isnan(chisq))
+	      psAbort("nan in chisq");
+
+	    for (psS32 n1 = 0; n1 < params->n; n1++) {
+	      if ((paramMask != NULL) && (paramMask->data.U8[n1])) {
+                continue;
+	      }
+	      psImage *dmodel = pcm->dmodelsConv->data[n1];
+	      float weight = dmodel->data.F32[i][j] * yweight;
+	      for (psS32 n2 = 0; n2 <= n1; n2++) {
+                if ((paramMask != NULL) && (paramMask->data.U8[n2])) {
+		  continue;
+                }
+		dmodel = pcm->dmodelsConv->data[n2];
+                alpha->data.F32[n1][n2] += weight * dmodel->data.F32[i][j];
+	      }
+	      beta->data.F32[n1] += weight * delta;
+	    }
+	}
+    }
+
+    // calculate lower-left half of alpha
+    for (psS32 j = 1; j < params->n; j++) {
+        for (psS32 k = 0; k < j; k++) {
+            alpha->data.F32[k][j] = alpha->data.F32[j][k];
+        }
+    }
+
+    // fill in pivots if we apply a mask
+    if (paramMask != NULL) {
+        for (psS32 j = 0; j < params->n; j++) {
+            if (paramMask->data.U8[j]) {
+                alpha->data.F32[j][j] = 1;
+                beta->data.F32[j] = 1;
+            }
+        }
+    }
+
+    return(chisq);
+}
+
+static void pmPCMDataFree (pmPCMData *pcm) {
+
+    if (pcm == NULL) return;
+
+    psFree (pcm->model);
+    psFree (pcm->modelConv);
+    psFree (pcm->dmodels);
+    psFree (pcm->dmodelsConv);
+    return;
+}
+
+pmPCMData *pmPCMDataAlloc (
+    const psVector *params,
+    const psVector *paramMask,
+    pmSource *source) {
+
+    pmPCMData *pcm = (pmPCMData *) psAlloc(sizeof(pmPCMData));
+    psMemSetDeallocator(pcm, (psFreeFunc) pmPCMDataFree);
+
+    // Allocate storage images for raw model and derivative images
+    pcm->model = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    pcm->dmodels = psArrayAlloc (params->n);
+    for (psS32 n = 0; n < params->n; n++) {
+      if ((paramMask != NULL) && (paramMask->data.U8[n])) { continue; }
+      pcm->dmodels->data[n] = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    }
+
+    // Allocate storage images for convolved model and derivative images
+    pcm->modelConv = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    pcm->dmodelsConv = psArrayAlloc (params->n);
+    for (psS32 n = 0; n < params->n; n++) {
+      if ((paramMask != NULL) && (paramMask->data.U8[n])) { continue; }
+      pcm->dmodelsConv->data[n] = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    }
+
+    return pcm;
+}
+
+psImage *pmPCMDataSaveImage (pmPCMData *pcm) {
+
+    psImage *model = psImageCopy (NULL, pcm->modelConv, PS_TYPE_F32);
+   
+    return model;
+}
+
+/*
+ *
+ * we have a function func(param; value)
+ 
+ * basic LMM:
+ 
+ - fill in the data (x, y)
+ 
+ chisq = SetABX (alpha, beta, params, paramMask, x, y, dy, func)
+ 
+ while () {
+   GuessABP (Alpha, Beta, Params, alpha, beta, params, paramMask, checkLimits, lambda)
+   dLinear = dLinear(Beta, beta, lambda);
+   chisq = SetABX (alpha, beta, params, paramMask, x, y, dy, func)
+   convergence tests...
+ }
+ 
+ 
+
+ ** GuessABP:
+
+      f_c = sum_i (kern_i * func (x_i; p_o))
+
+df_c/dp_o = d/dp_o [sum_i (kern_i * func (x_i; p_o))]
+
+df_c/dp_o = sum_i (d/dp_o [kern_i * func (x_i; p_o)])
+
+df_c/dp_o = sum_i (kern_i * d/dp_o [func (x_i; p_o)])
+
+- generate image arrays for func, dfunc/dp_j (not masked)
+- convolve each with psf
+- measure delta = f_conv - data
+- etc
+*/
+
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMosaicChip.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMosaicChip.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMosaicChip.c	(revision 22147)
@@ -0,0 +1,37 @@
+# include "psphotInternal.h"
+
+bool psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile)
+{
+    bool status;                        // Status of MD lookup
+
+    pmFPAfile *in = psMetadataLookupPtr(&status, config->files, inFile); // Input file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmFPAfile *out = psMetadataLookupPtr(&status, config->files, outFile); // Output file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // XXXX we are failing to get the output hdu right
+    pmChip *outChip = pmFPAviewThisChip(view, out->fpa);
+    pmChip *inChip = pmFPAviewThisChip(view, in->fpa);
+    if (!outChip->hdu && !outChip->parent->hdu) {
+        const char *name = psMetadataLookupStr(&status, in->fpa->concepts, "FPA.NAME"); // Name of FPA
+        pmFPAAddSourceFromView(out->fpa, name, view, out->format);
+    }
+
+    // mosaic the chip, forcing a deep copy (resulting images are not subimages)
+    psTrace("pmChipMosaic", 5, "mosaic chip %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+            in->name, out->name, in->xBin, in->yBin, out->xBin, out->yBin);
+    status = pmChipMosaic(outChip, inChip, true, pmConfigMask("BLANK", config));
+    return status;
+}
+
+// XXX does this do everything needed?
+// * mask & weight
+// * loaded PSF model (in readout->analysis)
+// * loaded SRC sources (in readout->analysis)
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMosaicSubimage.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMosaicSubimage.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotMosaicSubimage.c	(revision 22147)
@@ -0,0 +1,46 @@
+# include "psphotInternal.h"
+
+// insert the source image into the outimage at Xo, Yo
+bool psphotMosaicSubimage (psImage *outImage, pmSource *source, int Xo, int Yo, int DX, int DY) {
+
+    psRegion inRegion, outRegion;
+    psImage *inImage = source->pixels;
+
+    // identify the region in the output image
+    outRegion = psRegionSet (Xo, Xo + DX, Yo, Yo + DX);
+    outRegion = psRegionForImage (outImage, outRegion);
+    int DXo = outRegion.x1 - outRegion.x0;
+    int DYo = outRegion.y1 - outRegion.y0;
+    if (DXo <= 0) return false;
+    if (DYo <= 0) return false;
+    
+    // center the input source in the output box
+    int dX = (DXo - 1) / 2;
+    int dY = (DYo - 1) / 2;
+
+    // int xo = inImage->col0 + inImage->numCols / 2;
+    // int yo = inImage->row0 + inImage->numRows / 2;
+
+    int xo = source->peak->xf;
+    int yo = source->peak->yf;
+
+    // adjust region to overlay input image pixels
+    inRegion = psRegionSet (xo - dX, xo + dX + 1, yo - dY, yo + dY + 1);
+    inRegion = psRegionForImage (inImage, inRegion);
+
+    float peak = source->peak->flux;
+
+    psImage *subImage = psImageSubset (inImage, inRegion);
+    psImage *newImage = psImageAlloc (subImage->numCols, subImage->numRows, PS_TYPE_F32);
+    for (int iy = 0; iy < newImage->numRows; iy++) {
+	for (int ix = 0; ix < newImage->numCols; ix++) {
+	    newImage->data.F32[iy][ix] = subImage->data.F32[iy][ix] / peak;
+	}
+    }
+
+    psImageOverlaySection (outImage, newImage, Xo, Yo, "=");
+
+    psFree (subImage);
+    psFree (newImage);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotOutput.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotOutput.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotOutput.c	(revision 22147)
@@ -0,0 +1,158 @@
+# include "psphotInternal.h"
+
+pmReadout *psphotSelectBackground (pmConfig *config,
+                                   const pmFPAview *view,
+                                   const bool stdev // return background's standard deviation?
+                                   ) {
+
+    bool status;
+    pmReadout *background;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKMDL");
+    if (!file) return NULL;
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        background = file->readout;
+    } else {
+        background = pmFPAviewThisReadout (view, file->fpa);
+    }
+    return background;
+}
+
+// XXX replace this with a call to a pmConfig function (pmConfigDump...)
+bool psphotDumpConfig (pmConfig *config) {
+
+  psMetadataConfigWrite (config->complete, "complete.md");
+  psMetadataConfigWrite (config->camera, "camera.md");
+  psMetadataConfigWrite (config->recipes, "recipes.md");
+  psMetadataConfigWrite (config->arguments, "arguments.md");
+  psMetadataConfigWrite (config->files, "files.md");
+  return true;
+}
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteImage (fits, NULL, image, 0, NULL);
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+bool psphotDumpMoments (psMetadata *recipe, psArray *sources) {
+
+    bool status;
+
+    // optional dump of all rough source data
+    char *output = psMetadataLookupStr (&status, recipe, "MOMENTS_OUTPUT_FILE");
+    if (!status) return false;
+    if (output == NULL) return false;
+    if (output[0] == 0) return false;
+
+    pmMomentsWriteText (sources, output);
+    return true;
+}
+
+bool psphotDumpSource (pmSource *source, char *name) {
+
+    FILE *f = fopen (name, "w");
+    if (f == NULL) psAbort("can't open file");
+
+    for (int i = 0; i < source->pixels->numRows; i++) {
+        for (int j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->maskObj->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+
+            fprintf (f, "%d %d %f %f %d\n",
+                     (j + source->pixels->col0),
+                     (i + source->pixels->row0),
+                     source->pixels->data.F32[i][j],
+                     1.0 / source->weight->data.F32[i][j],
+                     source->maskObj->data.U8[i][j]);
+        }
+    }
+    fclose (f);
+    return true;
+}
+
+bool psphotAddPhotcode (psMetadata *recipe, pmConfig *config, const pmFPAview *view) {
+
+    bool status;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    PS_ASSERT (status, false);
+
+    // determine PHOTCODE from fpa & view, overwrite in recipe
+    char *photcode = pmConceptsPhotcodeForView (config, input, view);
+    PS_ASSERT (photcode, false);
+
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "photcode from FPA concepts", photcode);
+    psLogMsg ("psphot", 3, "PHOTCODE is %s", photcode);
+
+    psFree (photcode);
+    return true;
+}
+
+bool psphotSetHeaderNstars (psMetadata *recipe, psArray *sources) {
+
+    int nSrc = 0;
+
+    // count the number of sources which will be written
+    for (int i = 0; (sources != NULL) && (i < sources->n); i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL)
+            continue;
+        nSrc ++;
+    }
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NSTARS", PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS", nSrc);
+    return true;
+}
+
+// these values are saved in an output header stub - they are added to either the
+// PHU header (CMP) or the MEF table header (CMF)
+psMetadata *psphotDefineHeader (psMetadata *recipe) {
+
+    psMetadata *header = psMetadataAlloc ();
+
+    // write necessary information to output header
+    psMetadataItemSupplement (header, recipe, "ZERO_PT");
+    psMetadataItemSupplement (header, recipe, "PHOTCODE");
+
+    psMetadataItemSupplement (header, recipe, "APMIFIT");
+    psMetadataItemSupplement (header, recipe, "DAPMIFIT");
+    psMetadataItemSupplement (header, recipe, "NAPMIFIT");
+    psMetadataItemSupplement (header, recipe, "SKYBIAS");
+    psMetadataItemSupplement (header, recipe, "SKYSAT");
+
+    // PSF model parameters (shape values for image center)
+    psMetadataItemSupplement (header, recipe, "NPSFSTAR");
+    psMetadataItemSupplement (header, recipe, "APLOSS");
+    psMetadataItemSupplement (header, recipe, "FWHM_X");
+    psMetadataItemSupplement (header, recipe, "FWHM_Y");
+    psMetadataItemSupplement (header, recipe, "ANGLE");
+
+    // XXX these need to be defined from elsewhere
+    psMetadataAdd (header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+    psMetadataItemSupplement (header, recipe, "NSTARS");
+
+    // sky background model statistics
+    psMetadataItemSupplement (header, recipe, "MSKY_MN");
+    psMetadataItemSupplement (header, recipe, "MSKY_SIG");
+    psMetadataItemSupplement (header, recipe, "MSKY_MIN");
+    psMetadataItemSupplement (header, recipe, "MSKY_MAX");
+    psMetadataItemSupplement (header, recipe, "MSKY_NX");
+    psMetadataItemSupplement (header, recipe, "MSKY_NY");
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "DT_PHOT", PS_META_REPLACE, "elapsed psphot time", psTimerMark ("psphotReadout"));
+
+    // XXX : don't require any of these about values to exist
+    psErrorClear ();
+
+    return header;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPSFConvModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPSFConvModel.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPSFConvModel.c	(revision 22147)
@@ -0,0 +1,131 @@
+# include "psphot.h"
+
+// save as static values so they may be set externally
+static psF32 PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = 15;
+static psF32 PM_SOURCE_FIT_MODEL_TOLERANCE = 0.1;
+// static psF32 PM_SOURCE_FIT_MODEL_WEIGHT = 1.0;
+// static bool  PM_SOURCE_FIT_MODEL_PIX_WEIGHTS = true;
+
+// input source has both modelPSF and modelEXT.  on successful exit, we set the
+// modelConv to contain the fitted parameters, and the modelFlux to contain the 
+// convolved model image.
+bool psphotPSFConvModel (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+    
+    bool status;
+
+    int psfSize = psMetadataLookupS32 (&status, recipe, "PCM_BOX_SIZE");
+    if (!status) {
+	psfSize = 2;
+    }
+
+    // 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
+    // XXX for the moment, hard-wire the kernel to be 5x5 (2 pix radius)
+    // XXX for the moment, hard-wire the kernel to be 9x9 (4 pix radius)
+    psKernel *psf = psphotKernelFromPSF (source, psfSize);
+
+    // psf must be normalized (integral = 1.0)
+    double sum = 0.0;
+    for (int i = 0; i < psf->image->numRows; i++) {
+	for (int j = 0; j < psf->image->numCols; j++) {
+	    sum += psf->image->data.F32[i][j];
+	}
+    }
+    assert (sum > 0.0);
+    for (int i = 0; i < psf->image->numRows; i++) {
+	for (int j = 0; j < psf->image->numCols; j++) {
+	    psf->image->data.F32[i][j] /= sum;
+	}
+    }
+
+# if (0)
+    // XXX sanity check: convolve with delta function should behave like unconvolved version
+    for (int i = 0; i < psf->image->numRows; i++) {
+	for (int j = 0; j < psf->image->numCols; j++) {
+	    psf->image->data.F32[i][j] = 0.0;
+	}
+    }
+    psf->image->data.F32[2][2] = 1.0;
+# endif
+
+    // generate copy of the model
+    // XXX we could modify the parameter values or even the model 
+    // here based on the observed seeing (some lookup table...)
+    pmModel *modelConv = pmModelCopy (source->modelEXT);
+    psVector *params  = modelConv->params;
+    psVector *dparams = modelConv->dparams;
+
+    // create the minimization constraints
+    psMinConstraint *constraint = psMinConstraintAlloc();
+    constraint->paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
+    constraint->checkLimits = modelConv->modelLimits;
+
+    // set parameter mask based on fitting mode
+    // we fit a model without a floating sky term
+    int nParams = params->n - 1;
+    psVectorInit (constraint->paramMask, 0);
+    constraint->paramMask->data.U8[PM_PAR_SKY] = 1;
+
+    // force the floating parameters to fall within the contraint ranges
+    for (int i = 0; i < params->n; i++) {
+	modelConv->modelLimits (PS_MINIMIZE_PARAM_MIN, i, params->data.F32, NULL);
+	modelConv->modelLimits (PS_MINIMIZE_PARAM_MAX, i, params->data.F32, NULL);
+    }
+
+    // set up the minimization process
+    psMinimization *myMin = psMinimizationAlloc (PM_SOURCE_FIT_MODEL_NUM_ITERATIONS, PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F32);
+
+    bool fitStatus = psphotModelWithPSF_LMM (myMin, covar, params, constraint, source, psf, modelConv->modelFunc);
+    for (int i = 0; i < dparams->n; i++) {
+        if (psTraceGetLevel("psphot") >= 4) {
+            fprintf (stderr, "%f ", params->data.F32[i]);
+        }
+        if ((constraint->paramMask != NULL) && constraint->paramMask->data.U8[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F32[i][i]);
+    }
+    psTrace ("psphot", 4, "niter: %d, chisq: %f", myMin->iter, myMin->value);
+
+    // renormalize output model image
+    float Io = params->data.F32[PM_PAR_I0];
+    for (int iy = 0; iy < source->modelFlux->numRows; iy++) {
+	for (int ix = 0; ix < source->modelFlux->numCols; ix++) {
+	    source->modelFlux->data.F32[iy][ix] /= Io;
+	}
+    }
+
+    // save the resulting chisq, nDOF, nIter
+    modelConv->chisq = myMin->value;
+    modelConv->nIter = myMin->iter;
+
+    // XXX I actually need to count the number of unmasked pixels here
+    modelConv->nDOF  = source->pixels->numCols*source->pixels->numRows  -  nParams;
+
+    modelConv->flags |= PM_MODEL_STATUS_FITTED;
+    if (!fitStatus) modelConv->flags |= PM_MODEL_STATUS_NONCONVERGE;
+
+    // models can go insane: reject these
+    bool onPic = true;
+    onPic &= (params->data.F32[PM_PAR_XPOS] >= source->pixels->col0);
+    onPic &= (params->data.F32[PM_PAR_XPOS] <  source->pixels->col0 + source->pixels->numCols);
+    onPic &= (params->data.F32[PM_PAR_YPOS] >= source->pixels->row0);
+    onPic &= (params->data.F32[PM_PAR_YPOS] <  source->pixels->row0 + source->pixels->numRows);
+    if (!onPic) {
+        modelConv->flags |= PM_MODEL_STATUS_OFFIMAGE;
+    }
+
+    source->mode |= PM_SOURCE_MODE_FITTED;
+    source->modelConv = modelConv;
+
+    psFree(myMin);
+    psFree(covar);
+    psFree(constraint);
+
+    bool retval = (onPic && fitStatus);
+    psTrace("psphot", 5, "---- %s(%d) end ----\n", __func__, retval);
+    return(retval);
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPSFResiduals.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPSFResiduals.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPSFResiduals.c	(revision 22147)
@@ -0,0 +1,38 @@
+# include "psphotInternal.h"
+
+// construct an initial PSF model for each object 
+bool psphotResidPSF (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+  psTimerStart ("psphot");
+
+  for (int i = 0; i < sources->n; i++) {
+    pmSource *source = sources->data[i];
+
+    // only use the psf stars to build the residual model
+    if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	Xo = source->moments->x;
+	Yo = source->moments->y;
+	Io = source->peak->flux;
+    } else {
+	Xo = source->peak->xf;
+	Yo = source->peak->yf;
+	Io = source->peak->flux;
+    }
+
+    // set PSF parameters for this model (apply 2D shape model)
+    pmModel *modelPSF = pmModelFromPSFforXY (psf, Xo, Yo, Io);
+
+    // XXX need to define the guess flux?
+    // set the fit radius based on the object flux limit and the model
+    psphotCheckRadiusPSF (readout, source, modelPSF);
+
+    // set the source PSF model
+    source->modelPSF = modelPSF;
+  }
+  psLogMsg ("psphot.models", 4, "built models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+  return true;
+}
+
+// XXX do we always know which model is supposed to be used?  
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotParseCamera.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotParseCamera.c	(revision 22147)
@@ -0,0 +1,63 @@
+# include "psphotStandAlone.h"
+
+// define the needed / desired I/O files
+bool psphotParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the file to be loaded may have subdivisions at the cell and readout level
+    // we load into pmFPAfile *load, then reformat into pmFPAfile *input
+    pmFPAfile *load = pmFPAfileDefineFromArgs (&status, config, "PSPHOT.LOAD", "INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_CONFIG, false, "Failed to build FPA from PSPHOT.LOAD");
+	return status;
+    }
+    load->dataLevel = PM_FPA_LEVEL_CHIP; // force load at the CHIP level
+
+    // if MASK or WEIGHT was supplied on command line, bind files to 'load'
+    // the mask and weight will be mosaicked with the image
+    pmFPAfileBindFromArgs (&status, load, config, "PSPHOT.MASK",   "MASK");
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return NULL;
+    }
+
+    pmFPAfileBindFromArgs (&status, load, config, "PSPHOT.WEIGHT", "WEIGHT");
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return NULL;
+    }
+
+    // the psphot analysis is performed on chips
+    pmFPAfile *input = pmFPAfileDefineChipMosaic(config, load->fpa, "PSPHOT.INPUT");
+    if (!input) {
+        psError(PSPHOT_ERR_CONFIG, false, _("Unable to generate new file from PSPHOT.INPUT"));
+        return NULL;
+    }
+
+    // define the additional input/output files associated with psphot 
+    if (!psphotDefineFiles (config, input)) {
+	psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files");
+	return false;
+    }
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	// select on the basis of extname?
+	pmFPASelectChip (load->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(load->fpa, chipNum, false)) {
+		psError(PSPHOT_ERR_CONFIG, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+    psTrace("psphot", 1, "Done with psphotParseCamera...\n");
+
+    psErrorClear();			// some metadata lookup may have failed
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPetrosian.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPetrosian.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotPetrosian.c	(revision 22147)
@@ -0,0 +1,82 @@
+# include "psphot.h"
+
+bool psphotPetrosian (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  bool status;
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *flux = source->extpars->profile->flux;
+
+  // flux at which to measure isophotal parameters
+  // XXX cache this?
+  float PETROSIAN_R0 = psMetadataLookupF32 (&status, recipe, "PETROSIAN_R0");
+  float PETROSIAN_RF = psMetadataLookupF32 (&status, recipe, "PETROSIAN_FLUX_RATIO");
+
+  // first find flux at R0
+  int firstBelow = -1;
+  int lastAbove = -1;
+  for (int i = 0; i < radius->n; i++) {
+    if (radius->data.F32[i] > PETROSIAN_R0) lastAbove = i;
+    if ((firstBelow < 0) && (flux->data.F32[i] < PETROSIAN_R0)) firstBelow = i;
+  }
+
+  // average flux in this range
+  float fluxR0 = 0.0;
+  int fluxRn = 0;
+  for (int i = PS_MIN(firstBelow, lastAbove); i <= PS_MAX(firstBelow, lastAbove); i++) {
+    fluxR0 += flux->data.F32[i];
+    fluxRn ++;
+  }
+  fluxR0 /= (float)(fluxRn);
+  
+  // target flux for petrosian radius
+  float fluxRP = fluxR0 * PETROSIAN_RF;
+
+  // find the first bin below the flux level and the last above the level
+  // XXX can this be done faster with bisection?
+  // XXX do I need to worry about crazy outliers?  
+  // XXX should i be smoothing or fitting the curve?
+  firstBelow = -1;
+  lastAbove = -1;
+  for (int i = 0; i < flux->n; i++) {
+    if (flux->data.F32[i] > fluxRP) lastAbove = i;
+    if ((firstBelow < 0) && (flux->data.F32[i] < fluxRP)) firstBelow = i;
+  }
+
+  // need to examine pixels in this vicinity
+  float fluxFirst = 0;
+  float fluxLast = 0;
+  for (int i = 0; i <= PS_MAX(firstBelow, lastAbove); i++) {
+    if (i <= firstBelow) {
+      fluxFirst += flux->data.F32[i];
+    }
+    if (i <= lastAbove) {
+      fluxLast += flux->data.F32[i];
+    }
+  }
+  float fluxRPSum    = 0.5*(fluxLast + fluxFirst);
+  float fluxRPSumErr = 0.5*fabs(fluxLast - fluxFirst);
+  // XXX need to use the weight appropriately here...
+
+  float rad     = 0.5*(radius->data.F32[firstBelow] + radius->data.F32[lastAbove]);
+  float radErr  = 0.5*fabs(radius->data.F32[firstBelow] - radius->data.F32[lastAbove]);
+
+  if (!source->extpars->petrosian) {
+    source->extpars->petrosian = pmSourcePetrosianValuesAlloc ();
+  }
+  
+  // these are uncalibrated: instrumental mags and pixel units
+  source->extpars->petrosian->mag    = -2.5*log10(fluxRPSum);
+  source->extpars->petrosian->magErr = fluxRPSumErr / fluxRPSum;
+
+  source->extpars->petrosian->rad    = rad;
+  source->extpars->petrosian->radErr = radErr;
+
+  return true;
+
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadialPlot.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadialPlot.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadialPlot.c	(revision 22147)
@@ -0,0 +1,118 @@
+# include "psphotInternal.h"
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+
+# include <kapa.h>
+
+static int nCount = 0;
+
+bool psphotRadialPlot (int *kapa, const char *filename, pmSource *source) {
+
+    Graphdata graphdata;
+
+    // only plot 50 stars for now...
+    if (nCount > 00) {
+	if (*kapa != 0) {
+	    KiiClose (*kapa);
+	    *kapa = 0;
+	}
+	return true;
+    }
+
+    // XXX get the 'showWindow' option from the recipes somewhere
+    // XXX 'showWindow = false' is broken
+    if (*kapa == 0) {
+	*kapa = pmKapaOpen (false);
+	KapaResize (*kapa, 500, 500);
+	unlink (filename);
+    }
+    if (*kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (*kapa);
+
+    // examine sources to set data range
+    graphdata.xmin =  -0.05;
+    graphdata.xmax = +30.05;
+    graphdata.ymin = -0.05;
+    graphdata.ymax = +5.05;
+    KapaSetLimits (*kapa, &graphdata);
+  
+    KapaSetFont (*kapa, "helvetica", 14);
+    KapaBox (*kapa, &graphdata);
+    KapaSendLabel (*kapa, "radius (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (*kapa, "log flux (counts)", KAPA_LABEL_YM);
+	       
+    int nPts = source->pixels->numRows * source->pixels->numCols;
+    psVector *rg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *rb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+
+    int ng = 0;
+    int nb = 0;
+    float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS] - source->pixels->col0;
+    float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS] - source->pixels->row0;
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	for (int ix = 0; ix < source->pixels->numCols; ix++) {
+	    if (source->maskObj->data.U8[iy][ix]) {
+		rb->data.F32[nb] = hypot (ix - Xo, iy - Yo) ;
+		fb->data.F32[nb] = log10(source->pixels->data.F32[iy][ix]);
+		nb++;
+	    } else {
+		rg->data.F32[ng] = hypot (ix - Xo, iy - Yo) ;
+		fg->data.F32[ng] = log10(source->pixels->data.F32[iy][ix]);
+		ng++;
+	    }
+	}
+    }
+  
+    // set the plot range here based on lflux, radius
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (*kapa, ng, &graphdata);
+    KapaPlotVector (*kapa, ng, rg->data.F32, "x");
+    KapaPlotVector (*kapa, ng, fg->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (*kapa, nb, &graphdata);
+    KapaPlotVector (*kapa, nb, rb->data.F32, "x");
+    KapaPlotVector (*kapa, nb, fb->data.F32, "y");
+  
+    psLogMsg ("psphot", 3, "saving plot to %s", filename);
+
+    char pagename[16];
+    sprintf (pagename, "%02d", nCount);
+    if (nCount == 0) {
+	KiiPS (*kapa, filename, false, KAPA_PS_NEWPLOT, pagename);
+    } else {
+	KiiPS (*kapa, filename, false, KAPA_PS_NEWPAGE, pagename);
+    }
+
+    psFree (rg);
+    psFree (fg);
+    psFree (rb);
+    psFree (fb);
+
+    nCount ++;
+    return true;
+}
+
+# else
+
+bool psphotRadialPlot (int *kapa, const char *filename, pmSource *source) {
+    psLogMsg ("psphot", 3, "skipping source radial plots");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadialProfile.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadialProfile.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadialProfile.c	(revision 22147)
@@ -0,0 +1,62 @@
+# include "psphotInternal.h"
+
+# define COMPARE_RADIUS(A,B) (radius->data.F32[A] < radius->data.F32[B])
+# define SWAP_RADIUS(TYPE,A,B) { \
+  float tmp; \
+  if (A != B) { \
+    tmp = radius->data.F32[A]; \
+    radius->data.F32[A] = radius->data.F32[B]; \
+    radius->data.F32[B] = tmp; \
+    tmp = flux->data.F32[A]; \
+    flux->data.F32[A] = flux->data.F32[B]; \
+    flux->data.F32[B] = tmp; \
+    tmp = weight->data.F32[A]; \
+    weight->data.F32[A] = weight->data.F32[B]; \
+    weight->data.F32[B] = tmp; \
+  } \
+}
+
+bool psphotRadialProfile (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+    // allocate pmSourceExtendedParameters, if not already defined
+    if (!source->extpars) {
+        source->extpars = pmSourceExtendedParsAlloc ();
+    }
+
+    if (!source->extpars->profile) {
+        source->extpars->profile = pmSourceRadialProfileAlloc ();
+    }
+
+    int nPts = source->pixels->numRows * source->pixels->numCols;
+    source->extpars->profile->radius = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    source->extpars->profile->flux   = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    source->extpars->profile->weight = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+
+    psVector *radius = source->extpars->profile->radius;
+    psVector *flux   = source->extpars->profile->flux;
+    psVector *weight = source->extpars->profile->weight;
+
+    // XXX use the extended source model here for Xo, Yo?
+    // XXX define a radius scaled to the elliptical contour?
+
+    int n = 0;
+    float Xo = source->modelEXT->params->data.F32[PM_PAR_XPOS] - source->pixels->col0;
+    float Yo = source->modelEXT->params->data.F32[PM_PAR_YPOS] - source->pixels->row0;
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+        for (int ix = 0; ix < source->pixels->numCols; ix++) {
+            if (source->maskObj->data.U8[iy][ix]) continue;
+            radius->data.F32[n] = hypot (ix - Xo, iy - Yo) ;
+            flux->data.F32[n]   = source->pixels->data.F32[iy][ix];
+            weight->data.F32[n] = source->weight->data.F32[iy][ix];
+            n++;
+        }
+    }
+    radius->n = n;
+    weight->n = n;
+    flux->n = n;
+
+    // sort the vector set by the radius
+    PSSORT (radius->n, COMPARE_RADIUS, SWAP_RADIUS, NONE);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadiusChecks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadiusChecks.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRadiusChecks.c	(revision 22147)
@@ -0,0 +1,106 @@
+# include "psphotInternal.h"
+# define RADIUS_TYPE int
+
+static float PSF_FIT_NSIGMA;
+static float PSF_FIT_PADDING;
+static float PSF_FIT_RADIUS = 0;	// radius to use in fitting (ignored if <= 0,
+					// and a per-object radius is calculated)
+
+bool psphotInitRadiusPSF(const psMetadata *recipe,
+			 const pmModelType type)
+{
+    bool status = true;
+
+    PSF_FIT_NSIGMA  = psMetadataLookupF32(&status, recipe, "PSF_FIT_NSIGMA");
+    PSF_FIT_PADDING = psMetadataLookupF32(&status, recipe, "PSF_FIT_PADDING");
+    PSF_FIT_RADIUS =  psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS");
+
+    return true;
+}
+
+// call this function whenever you (re)-define the PSF model
+bool psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // XXX do we have a better value for the sky noise level?  not really...
+    pmMoments *moments = source->moments;
+
+    // set the fit radius based on the object flux limit and the model
+    float radiusFit = PSF_FIT_RADIUS;
+    if (radiusFit <= 0) {		// use fixed radius
+	if (moments == NULL) {
+	    radiusFit = model->modelRadius(model->params, PSF_FIT_NSIGMA*moments->dSky);
+	} else {
+	    radiusFit = model->modelRadius(model->params, 1.0);
+	}
+    }
+    model->radiusFit = (RADIUS_TYPE)(radiusFit + PSF_FIT_PADDING);
+
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+	
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	model->radiusFit *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit, "OR", PM_MASK_MARK);
+    return status;
+}
+
+bool psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR) {
+
+    psF32 *PAR = model->params->data.F32;
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radiusFit = (RADIUS_TYPE) (model->modelRadius (model->params, PSF_FIT_NSIGMA*moments->dSky) + dR + PSF_FIT_PADDING);
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+	
+    if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+	model->radiusFit *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit, "OR", PM_MASK_MARK);
+    return status;
+}
+
+static float EXT_FIT_NSIGMA;
+static float EXT_FIT_PADDING;
+
+bool psphotInitRadiusEXT (psMetadata *recipe, pmModelType type) {
+
+    bool status;
+
+    EXT_FIT_NSIGMA   = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA");
+    EXT_FIT_PADDING  = psMetadataLookupF32 (&status, recipe, "EXT_FIT_PADDING");
+
+    return true;
+}
+
+// call this function whenever you (re)-define the EXT model
+bool psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model) {
+
+    psF32 *PAR = model->params->data.F32;
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radiusFit = (RADIUS_TYPE) (model->modelRadius (model->params, EXT_FIT_NSIGMA*moments->dSky) + EXT_FIT_PADDING);
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+
+    // redefine the pixels if needed
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit, "OR", PM_MASK_MARK);
+    return status;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReadout.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReadout.c	(revision 22147)
@@ -0,0 +1,207 @@
+# include "psphotInternal.h"
+
+bool psphotReadout(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
+    // for psphot envisions threading within psphotReadout, not multiple threads calling
+    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
+    // jointly by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (recipe, config, view)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, 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
+    psphotSetMaskAndWeight (config, readout, recipe);
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup(config, readout, recipe, NULL, NULL, NULL);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view, "PSPHOT.INPUT")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!psphotSubtractBackground (config, view, "PSPHOT.INPUT")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+
+    // run a single-model test if desired (exits from here if test is run)
+    psphotModelTest (config, view, recipe);
+
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved in the recipe
+    pmPSF *psf = psphotLoadPSF (config, view, recipe);
+
+    // several functions below behave differently if we have a PSF model already
+    bool havePSF = (psf != NULL);
+
+    // find the detections (by peak and/or footprint) in the image.
+    pmDetections *detections = psphotFindDetections (NULL, readout, recipe);
+    if (!detections) {
+        psLogMsg ("psphot", 3, "unable to find detections in this image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, NULL);
+    }
+
+    // construct sources and measure basic stats
+    psArray *sources = psphotSourceStats (readout, recipe, detections);
+    if (!sources) return false;
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // find blended neighbors of very saturated stars
+    // XXX merge this with Basic Deblend?
+    psphotDeblendSatstars (sources, recipe);
+
+    // mark blended peaks PS_SOURCE_BLEND
+    if (!psphotBasicDeblend (sources, recipe)) {
+        psLogMsg ("psphot", 3, "failed on deblend analysis");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // classify sources based on moments, brightness
+    if (!psphotRoughClass (sources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+    if (!strcasecmp (breakPt, "MOMENTS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // if we were not supplied a PSF, choose one here
+    if (psf == NULL) {
+        // use bright stellar objects to measure PSF
+        // XXX if we do not have enough stars to generate the PSF, build one
+        // from the SEEING guess and model class
+        psf = psphotChoosePSF (readout, sources, recipe);
+        if (psf == NULL) {
+            psLogMsg ("psphot", 3, "failure to construct a psf model");
+            return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        }
+        havePSF = true;
+    }
+    if (!strcasecmp (breakPt, "PSFMODEL")) {
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // include externally-supplied sources
+    psphotLoadExtSources (config, view, sources);
+
+    // construct an initial model for each object
+    psphotGuessModels (readout, sources, recipe, psf);
+
+    // XXX test output of models
+    // psphotTestSourceOutput (readout, sources, recipe, psf);
+
+    // linear PSF fit to source peaks
+    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
+
+    // XXX test the CR/EXT measurement
+    // XXX we need to call this here and after the second pass: option for starting number?
+    psphotSourceSize (readout, sources, recipe, 0);
+    if (!strcasecmp (breakPt, "ENSEMBLE")) {
+        goto finish;
+    }
+
+    // non-linear PSF and EXT fit to brighter sources
+    psphotBlendFit (readout, sources, recipe, psf);
+
+    // replace all sources (make this part of psphotFitSourcesLinear?)
+    psphotReplaceAll (sources, recipe);
+
+    // linear fit to include all sources
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+    if (!strcasecmp (breakPt, "PASS1")) {
+        goto finish;
+    }
+
+    // XXX for the moment, drop the re-calc of the background (prove this works)
+    // replace background in residual image
+    // psphotSkyReplace (config, view);
+    // re-measure background model (median, smoothed image)
+    // psphotImageMedian (config, view);
+
+    // add noise for subtracted objects
+    psphotAddNoise (readout, sources, recipe);
+
+    detections = psphotFindDetections (detections, readout, recipe);
+
+    // remove noise for subtracted objects
+    psphotSubNoise (readout, sources, recipe);
+
+    // define new sources based on only the new peaks
+    psArray *newSources = psphotSourceStats (readout, recipe, detections);
+
+    // set source type
+    if (!psphotRoughClass (newSources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // create full input models
+    psphotGuessModels (readout, newSources, recipe, psf);
+
+    // replace all sources so fit below applies to all at once
+    psphotReplaceAll (sources, recipe);
+
+    // merge the newly selected sources into the existing list
+    psphotMergeSources (sources, newSources);
+    psFree (newSources);
+
+    // linear fit to all sources
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+
+    // measure source size for the remaining sources
+    psphotSourceSize (readout, sources, recipe, 0);
+
+    psphotExtendedSources (readout, sources, recipe);
+
+finish:
+
+    // plot positive sources
+    if (!havePSF) {
+        // psphotSourcePlots (readout, sources, recipe);
+    }
+
+    // measure aperture photometry corrections
+    if (!havePSF && !psphotApResid (readout, sources, recipe, psf)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // calculate source magnitudes
+    pmReadout *background = psphotSelectBackground (config, view, false);
+    psphotMagnitudes(sources, recipe, psf, background);
+
+    // replace failed sources?
+    // psphotReplaceUnfit (sources);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (sources);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReadoutCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReadoutCleanup.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReadoutCleanup.c	(revision 22147)
@@ -0,0 +1,81 @@
+# include "psphotInternal.h"
+
+// psphotReadoutCleanup is called on exit from psphotReadout.  If the last raised error is
+// not a DATA error, then there was a serious problem.  Only in this case, or if the fail
+// on the stats measurement, do we return false
+bool psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmDetections *detections, pmPSF *psf, psArray *sources) {
+
+    // remove internal pmFPAfiles, if created
+    bool 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");
+	return false;
+    }
+
+    if (psErrorCodeLast() == PSPHOT_ERR_DATA) {
+        psErrorStackPrint(stderr, "Error in the psphot readout analysis");
+	psErrorClear();
+    } 
+    if (psErrorCodeLast() != PS_ERR_NONE) { 
+	psFree (psf);
+	psFree (sources);
+	return false;
+    }
+
+    // use the psf-model to measure FWHM stats
+    if (psf) {
+        if (!psphotPSFstats (readout, recipe, psf)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure PSF shape parameters");
+	    psFree (psf);
+	    psFree (sources);
+            return false;
+        }
+    }
+    // otherwise, use the source moments to measure FWHM stats
+    if (!psf && sources) {
+        if (!psphotMomentsStats (readout, recipe, sources)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure Moment shape parameters");
+	    psFree (sources);
+            return false;
+        }
+    }
+
+    // write NSTARS to the image header
+    psphotSetHeaderNstars (recipe, sources);
+
+    // create an output header with stats results
+    psMetadata *header = psphotDefineHeader (recipe);
+
+    // save the results of the analysis
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", header);
+    if (sources) {
+	psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY,    "psphot sources", sources);
+    }
+    if (psf) {
+	// save the psf for possible output.  if there was already an entry, it was loaded from external sources
+	// the new one may have been updated or modified, so replace the existing entry.  We
+	// are required to save it on the chip, but this will cause problems if we ever want to
+	// run psphot on an unmosaiced image
+	pmCell *cell = readout->parent;
+	pmChip *chip = cell->parent;
+	psMetadataAdd (chip->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_DATA_UNKNOWN | PS_META_REPLACE,  "psphot psf", psf);
+    }
+
+    if (psErrorCodeLast() != PS_ERR_NONE) {
+        psErrorStackPrint(stderr, "unexpected remaining errors");
+	abort();
+    }
+
+    // XXX move this to top of loop?
+    pmKapaClose ();
+
+    psFree (detections);
+    psFree (psf);
+    psFree (header);
+    psFree (sources);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReplaceUnfit.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReplaceUnfit.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotReplaceUnfit.c	(revision 22147)
@@ -0,0 +1,91 @@
+# include "psphotInternal.h"
+
+// replace the flux for sources which failed
+bool psphotReplaceUnfit (psArray *sources, psMaskType maskVal) {
+
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (source->mode & PM_SOURCE_MODE_FAIL) goto replace;
+      continue;
+
+    replace:
+        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode &= ~PM_SOURCE_MODE_SUBTRACTED;
+    }
+    psLogMsg ("psphot.replace", 3, "replace unfitted models: %f sec (%ld objects)\n", psTimerMark ("psphot"), sources->n);
+    return true;
+}
+
+bool psphotReplaceAll (psArray *sources, psMetadata *recipe) {
+
+    bool status;
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (!(source->mode & PM_SOURCE_MODE_SUBTRACTED)) continue;
+
+      pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+      source->mode &= ~PM_SOURCE_MODE_SUBTRACTED;
+    }
+    psLogMsg ("psphot.replace", PS_LOG_INFO, "replaced models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    return true;
+}
+
+// add source, if the source has been subtracted (or if we ignore the state)
+bool psphotAddWithTest (pmSource *source, bool useState, psMaskType maskVal) {
+
+    // what is current state? (true : add; false : sub)
+    bool state = !(source->mode & PM_SOURCE_MODE_SUBTRACTED);
+    if (state && useState) return true;
+
+    // replace the model if 1) state says it is missing or 2) useState is false (just do it)
+    if (!state || !useState) {
+        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    return true;
+}
+
+// sub source, if the source has been added (or if we ignore the state)
+bool psphotSubWithTest (pmSource *source, bool useState, psMaskType maskVal) {
+
+    // what is current state? (true : sub; false : add)
+    bool state = (source->mode & PM_SOURCE_MODE_SUBTRACTED);
+    if (state && useState) return true;
+
+    // replace the model if 1) state says it is missing or 2) useState is false (just do it)
+    if (!state || !useState) {
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    return true;
+}
+
+// add or sub source replace or if the source has
+bool psphotSetState (pmSource *source, bool curState, psMaskType maskVal) {
+
+    // what is desired state? (true : add; false : sub)
+    bool newState = !(source->mode & PM_SOURCE_MODE_SUBTRACTED);
+    if (curState == newState) return true;
+
+    if (curState && !newState) {
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    if (newState && !curState) {
+        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRoughClass.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRoughClass.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotRoughClass.c	(revision 22147)
@@ -0,0 +1,60 @@
+# include "psphotInternal.h"
+
+# define CHECK_STATUS(S,MSG) { \
+  if (!status) { \
+    psError(PSPHOT_ERR_CONFIG, false, "missing PSF Clump entry: %s\n", MSG); \
+    return false; \
+  } }
+
+// 2006.02.02 : no leaks
+bool psphotRoughClass (psArray *sources, psMetadata *recipe, const bool havePSF) {
+
+    bool status;
+    static pmPSFClump psfClump;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskSat = psMetadataLookupU8(&status, recipe, "MASK.SAT"); // Mask value for bad pixels
+    assert (maskSat);
+
+    if (!havePSF) {
+        // determine the PSF parameters from the source moment values
+        psfClump = pmSourcePSFClump (sources, recipe);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
+    } else {
+        // pull FWHM_X,Y from the recipe, use to define psfClump.X,Y
+        psfClump.X  = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.X");  CHECK_STATUS (status, "PSF.CLUMP.X");
+        psfClump.Y  = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.Y");  CHECK_STATUS (status, "PSF.CLUMP.Y");
+        psfClump.dX = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.DX");  CHECK_STATUS (status, "PSF.CLUMP.DX");
+        psfClump.dY = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.DY");  CHECK_STATUS (status, "PSF.CLUMP.DY");
+    }
+
+    if (psfClump.X < 0) {
+        psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourcePSFClump");
+        return false;
+    }
+    if (!psfClump.X || !psfClump.Y) {
+        psError(PSPHOT_ERR_DATA, true, "Failed to find a valid PSF clump");
+        return false;
+    }
+    psLogMsg ("psphot", 3, "psf clump  X,  Y: %f, %f\n", psfClump.X, psfClump.Y);
+    psLogMsg ("psphot", 3, "psf clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
+
+    // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
+    if (!pmSourceRoughClass (sources, recipe, psfClump, maskSat)) {
+        psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourceRoughClass");
+        return false;
+    }
+
+    // optional printout of source moments only
+    psphotDumpMoments (recipe, sources);
+
+    psLogMsg ("psphot.roughclass", PS_LOG_INFO, "rough classification: %f sec\n", psTimerMark ("psphot"));
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSavePSFStars.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSavePSFStars.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSavePSFStars.c	(revision 22147)
@@ -0,0 +1,148 @@
+# include "psphotInternal.h"
+
+bool psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    // bool status = false;
+    psTimerStart ("psphot");
+
+    // if this file is defined, create the necessary output data
+    pmFPAfile *file = psMetadataLookupPtr(&status, config->files, "PSPHOT.PSF.STARS");
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 21;
+    int DY = 21;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;				// starting corner of next box
+    int dY = 0;				// height of row so far
+    int NX = 20*DX;			// full width of output image
+    int NY = 0;				// total height of output image
+
+    // first, examine the PSF and SAT stars:
+    // - determine bounding boxes for summary image
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+	bool keep = false;
+        keep |= (source->mode & PM_SOURCE_MODE_PSFSTAR);
+        keep |= (source->mode & PM_SOURCE_MODE_SATSTAR);
+	if (!keep) continue;
+
+	// how does this subimage get placed into the output image?
+	// DX = source->pixels->numCols
+	// DY = source->pixels->numRows
+
+	if (dX + DX > NX) {
+	    // too wide for the rest of this row
+	    if (dX == 0) {
+		// alone on this row
+		NY += DY;
+		dX = 0;
+		dY = 0;
+	    } else {
+		// start the next row
+		NY += dY;
+		dX = DX;
+		dY = DY;
+	    }
+	} else {
+	    // extend this row
+	    dX += DX;
+	    dY = PS_MAX (dY, DY);
+	}
+    }
+
+    // allocate output image
+    psImage *outpos = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImage *outsub = psImageAlloc (NX, NY, PS_TYPE_F32);
+
+    int Xo = 0;				// starting corner of next box
+    int Yo = 0;				// starting corner of next box
+    dY = 0;				// height of row so far
+
+    int nPSF = 0;
+    int nSAT = 0;
+    int kapa = 0;			// file descriptor for plotting routine
+
+    // first, examine the PSF and SAT stars:
+    // - generate radial plots (PS plots)
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+	bool keep = false;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    nPSF ++;
+	    keep = true;
+	}
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    nSAT ++;
+	    keep = true;
+	}	    
+	if (!keep) continue;
+
+	// how does this subimage get placed into the output image?
+	// DX = source->pixels->numCols
+	// DY = source->pixels->numRows
+
+	if (Xo + DX > NX) {
+	    // too wide for the rest of this row
+	    if (Xo == 0) {
+		// place source alone on this row
+		psphotAddWithTest (source, true); // replace source if subtracted
+		psphotRadialPlot (&kapa, "radial.plots.ps", source);
+		psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+		psphotSubWithTest (source, false); // remove source (force)
+		psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+
+		psphotSetState (source, false); // replace source (has been subtracted)
+		Yo += DY;
+		Xo = 0;
+		dY = 0;
+	    } else {
+		// start the next row
+		Yo += dY;
+		Xo = 0;
+		psphotAddWithTest (source, true); // replace source if subtracted
+		psphotRadialPlot (&kapa, "radial.plots.ps", source);
+		psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+		psphotSubWithTest (source, false); // remove source (force)
+		psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+		psphotSetState (source, false); // replace source (has been subtracted)
+
+		Xo = DX;
+		dY = DY;
+	    }
+	} else {
+	    // extend this row
+	    psphotAddWithTest (source, true); // replace source if subtracted
+	    psphotRadialPlot (&kapa, "radial.plots.ps", source);
+	    psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+	    psphotSubWithTest (source, false); // remove source (force)
+	    psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+	    psphotSetState (source, false); // replace source (has been subtracted)
+
+	    Xo += DX;
+	    dY = PS_MAX (dY, DY);
+	}
+    }
+
+    psphotSaveImage (NULL, outpos, "outpos.fits");
+    psphotSaveImage (NULL, outsub, "outsub.fits");
+    psLogMsg ("psphot", PS_LOG_INFO, "plotted %d sources (%d psf, %d sat): %f sec\n", nPSF + nSAT, nPSF, nSAT, psTimerMark ("psphot"));
+
+    psFree (outpos);
+    psFree (outsub);
+    return (sources);
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSkyReplace.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSkyReplace.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSkyReplace.c	(revision 22147)
@@ -0,0 +1,33 @@
+# include "psphotInternal.h"
+
+// XXX make this an option?
+// in order to  successfully replace the sky, we must define a corresponding file...
+bool psphotSkyReplace (pmConfig *config, const pmFPAview *view) {
+
+    psTimerStart ("psphot");
+
+    // find the currently selected readout
+    pmReadout *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    if (readout == NULL) psAbort("input not defined");
+
+    // select background pixels, from output background file, or create
+    pmReadout *background = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKGND");
+    if (background == NULL) psAbort("background not defined");
+
+    // select the corresponding images
+    psF32 **image = readout->image->data.F32;
+    psU8  **mask  = readout->mask->data.U8;
+    psF32 **back  = background->image->data.F32;
+
+    // replace the background model
+    for (int j = 0; j < readout->image->numRows; j++) {
+        for (int i = 0; i < readout->image->numCols; i++) {
+            if (!mask[j][i]) {
+                image[j][i] += back[j][i];
+            }
+        }
+    }
+    psLogMsg ("psphot.sky", PS_LOG_DETAIL, "replace background flux : %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSortBySN.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSortBySN.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSortBySN.c	(revision 22147)
@@ -0,0 +1,1 @@
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceFits.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceFits.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceFits.c	(revision 22147)
@@ -0,0 +1,371 @@
+# include "psphotInternal.h"
+
+// given a source with an existing modelPSF, attempt a full PSF fit, subtract if successful
+// XXX this function does not call pmSourceFitModelInit : fix this?
+
+static int NfitPSF = 0;
+static int NfitBlend = 0;
+static int NfitDBL = 0;
+static int NfitEXT = 0;
+
+bool psphotFitInit () {
+    psTimerStart ("psphot.fits");
+    return true;
+}
+
+bool psphotFitSummary () {
+
+    fprintf (stderr, "fitted %5d psf, %5d blend, %5d ext, %5d dbl : %6.2f sec\n",
+             NfitPSF, NfitBlend, NfitEXT, NfitDBL, psTimerMark ("psphot.fits"));
+    return true;
+}
+
+bool psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal) {
+
+    float x, y, dR;
+
+    // if this source is not a possible blend, just fit as PSF
+    if ((source->blends == NULL) || (source->mode & PM_SOURCE_MODE_SATSTAR)) {
+        bool status = psphotFitPSF (readout, source, psf, maskVal);
+        return status;
+    }
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+
+    if (isnan(PSF->params->data.F32[PM_PAR_I0])) psAbort("nan in blend fit primary");
+
+    x = PSF->params->data.F32[PM_PAR_XPOS];
+    y = PSF->params->data.F32[PM_PAR_YPOS];
+
+    psArray *modelSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (modelSet, 16, PSF);
+
+    psArray *sourceSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (sourceSet, 16, source);
+
+    // we need to include all blends in the fit (unless primary is saturated?)
+    dR = 0;
+    for (int i = 0; i < source->blends->n; i++) {
+        pmSource *blend = source->blends->data[i];
+
+        // find the blend which is furthest from source
+        dR = PS_MAX (dR, hypot (blend->peak->xf - x, blend->peak->yf - y));
+
+        // create the model and guess parameters for this blend
+        pmModel *model = pmModelAlloc (PSF->type);
+        for (int j = 0; j < model->params->n; j++) {
+            model->params->data.F32[j] = PSF->params->data.F32[j];
+            model->dparams->data.F32[j] = PSF->dparams->data.F32[j];
+        }
+
+        // XXX assume local sky is 0.0?
+        model->params->data.F32[PM_PAR_I0] = blend->peak->flux;
+        model->params->data.F32[PM_PAR_XPOS] = blend->peak->xf;
+        model->params->data.F32[PM_PAR_YPOS] = blend->peak->yf;
+
+        // these should never be invalid values
+        // XXX drop these tests eventually
+        if (isnan(model->params->data.F32[PM_PAR_I0]))   psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_XPOS])) psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_YPOS])) psAbort("nan in blend fit");
+
+        // add this blend to the list
+        psArrayAdd (modelSet, 16, model);
+        psArrayAdd (sourceSet, 16, blend);
+
+        // free to avoid double counting model
+        psFree (model);
+    }
+
+    // extend source radius as needed
+    psphotCheckRadiusPSFBlend (readout, source, PSF, dR);
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF, maskVal);
+
+    // correct model chisq for flux trend
+    double chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // evaluate the blend objects, subtract if good, free otherwise
+    for (int i = 1; i < modelSet->n; i++) {
+        pmSource *blend = sourceSet->data[i];
+        pmModel *model  = modelSet->data[i];
+
+        // correct model chisq for flux trend
+        chiTrend = psPolynomial1DEval (psf->ChiTrend, model->params->data.F32[PM_PAR_I0]);
+        model->chisqNorm = model->chisq / chiTrend;
+
+        // if this one failed, skip it
+        if (!psphotEvalPSF (blend, model)) {
+            psTrace ("psphot", 4, "failed on blend %d of %ld\n", i, modelSet->n);
+            continue;
+        }
+
+        // otherwise, supply the resulting model to the corresponding blend
+        psFree(blend->modelPSF);
+        blend->modelPSF = psMemIncrRefCounter (model);
+        psTrace ("psphot", 5, "fitted blend as PSF\n");
+
+        // build cached model and subtract
+        pmSourceCacheModel (blend, maskVal);
+        pmSourceSub (blend, PM_MODEL_OP_FULL, maskVal);
+        blend->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    }
+    NfitBlend += modelSet->n;
+
+    // evaluate the primary object
+    if (!psphotEvalPSF (source, PSF)) {
+        psTrace ("psphot", 4, "failed on blend 0 of %ld\n", modelSet->n);
+        psFree (PSF);
+        psFree (modelSet);
+        psFree (sourceSet);
+        return false;
+    }
+    psFree (modelSet);
+    psFree (sourceSet);
+
+    // save the new, successful model
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    psTrace ("psphot", 5, "fitted primary as PSF\n");
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    return true;
+}
+
+bool psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal) {
+
+    double chiTrend;
+
+    NfitPSF ++;
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in psf fit");
+
+    // extend source radius as needed
+    psphotCheckRadiusPSF (readout, source, PSF);
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitModel (source, PSF, PM_SOURCE_FIT_PSF, maskVal);
+
+    // correct model chisq for flux trend
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // does the PSF model succeed?
+    if (!psphotEvalPSF (source, PSF)) {
+        psFree (PSF);
+        return false;
+    }
+
+    // free old model, save new model
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    psTrace ("psphot", 5, "fitted as PSF\n");
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    return true;
+}
+
+static float EXT_MIN_SN;
+static float EXT_MOMENTS_RAD;
+static pmModelType modelTypeEXT;
+
+bool psphotInitLimitsEXT (psMetadata *recipe) {
+
+    bool status;
+
+    // extended source model parameters
+    EXT_MIN_SN       = psMetadataLookupF32 (&status, recipe, "EXT_MIN_SN");
+    EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, recipe, "EXT_MOMENTS_RADIUS");
+
+    // extended source model descriptions
+    char *modelNameEXT = psMetadataLookupStr (&status, recipe, "EXT_MODEL");
+    modelTypeEXT = pmModelClassGetType (modelNameEXT);
+    psphotInitRadiusEXT (recipe, modelTypeEXT);
+
+    return true;
+}
+
+bool psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf, psMaskType maskVal) {
+
+    bool okEXT, okDBL;
+    float chiEXT, chiDBL;
+    double chiTrend;
+    pmModel *ONE = NULL;
+
+    // skip the source if we don't think it is extended
+    if (source->type == PM_SOURCE_TYPE_UNKNOWN) return false;
+    if (source->type == PM_SOURCE_TYPE_DEFECT) return false;
+    if (source->type == PM_SOURCE_TYPE_SATURATED) return false;
+    if (source->moments->SN < EXT_MIN_SN) return false;
+
+    // recalculate the source moments using the larger extended-source moments radius
+    if (!pmSourceMoments (source, EXT_MOMENTS_RAD)) return false;
+
+    psTrace ("psphot", 5, "trying blob...\n");
+
+    // psTraceSetLevel("psModules.objects.pmSourceFitSet", 5);
+
+    // this temporary source is used as a place-holder by the psphotEval functions below
+    pmSource *tmpSrc = pmSourceAlloc ();
+
+    pmModel *EXT = psphotFitEXT (readout, source, maskVal);
+    okEXT = psphotEvalEXT (tmpSrc, EXT);
+    chiEXT = EXT->chisq / EXT->nDOF;
+
+    psArray *DBL = psphotFitDBL (readout, source, maskVal);
+    okDBL  = psphotEvalDBL (tmpSrc, DBL->data[0]);
+    okDBL &= psphotEvalDBL (tmpSrc, DBL->data[1]);
+    // XXX should I keep / save the flags set in the eval functions?
+
+    // correct first model chisqs for flux trend
+    ONE = DBL->data[0];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    // save chisq for double-star/galaxy comparison
+    chiDBL = ONE->chisq / ONE->nDOF;
+
+    // correct second model chisqs for flux trend
+    ONE = DBL->data[1];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    psFree (tmpSrc);
+
+    // psTraceSetLevel("psModules.objects.pmSourceFitSet", 0);
+
+    if (okEXT && okDBL) {
+        psTrace ("psphot", 5, "blob chisq: %f vs %f\n", chiEXT, chiDBL);
+        // XXX EAM : a bogus bias: need to examine this better
+        if (3*chiEXT > chiDBL) goto keepDBL;
+        goto keepEXT;
+    }
+
+    if (okEXT && !okDBL) goto keepEXT;
+    if (!okEXT && okDBL) goto keepDBL;
+
+    // both models failed; reject them both
+    psFree (EXT);
+    psFree (DBL);
+    return false;
+
+keepEXT:
+    // sub EXT
+    psFree (DBL);
+
+    // save new model
+    source->modelEXT = EXT;
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    psTrace ("psphot", 5, "blob as EXT: %f %f\n", EXT->params->data.F32[PM_PAR_XPOS], EXT->params->data.F32[PM_PAR_YPOS]);
+    return true;
+
+keepDBL:
+    // sub DLB
+    psFree (EXT);
+
+    // drop old model, save new second model...
+    psFree (source->modelPSF);
+    source->modelPSF = psMemIncrRefCounter (DBL->data[0]);
+    source->mode    |= PM_SOURCE_MODE_SUBTRACTED;
+    source->mode    |= PM_SOURCE_MODE_PAIR;
+
+    // copy most data from the primary source (modelEXT, blends stay NULL)
+    // XXX use pmSourceCopy?
+    pmSource *newSrc = pmSourceCopy (source);
+    newSrc->modelPSF = psMemIncrRefCounter (DBL->data[1]);
+
+    // build cached models and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    pmSourceCacheModel (newSrc, maskVal);
+    pmSourceSub (newSrc, PM_MODEL_OP_FULL, maskVal);
+    psTrace ("psphot", 5, "blob as DBL: %f %f\n", ONE->params->data.F32[PM_PAR_XPOS], ONE->params->data.F32[PM_PAR_YPOS]);
+
+    psArrayAdd (sources, 100, newSrc);
+    psFree (newSrc);
+    psFree (DBL);
+    return true;
+}
+
+// fit a double PSF source to an extended blob
+psArray *psphotFitDBL (pmReadout *readout, pmSource *source, psMaskType maskVal) {
+
+    float dx, dy;
+    pmModel *DBL;
+    pmModel *PSF;
+    psEllipseAxes axes;
+    psEllipseMoments moments;
+    psArray *modelSet;
+
+    NfitDBL ++;
+
+    // make a guess at the position of the two sources
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    // XXX this is really arbitrary: 4 pixel separation?
+    dx = 2 * cos (axes.theta);
+    dy = 2 * sin (axes.theta);
+
+    // save the PSF model from the Ensemble fit
+    PSF = source->modelPSF;
+    psphotCheckRadiusPSFBlend (readout, source, PSF, 8.0);
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in dbl fit");
+
+    modelSet = psArrayAlloc (2);
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf + dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf + dy;
+    modelSet->data[0] = DBL;
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf - dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf - dy;
+    modelSet->data[1] = DBL;
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF, maskVal);
+    return (modelSet);
+}
+
+pmModel *psphotFitEXT (pmReadout *readout, pmSource *source, psMaskType maskVal) {
+
+    NfitEXT ++;
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *EXT = pmSourceModelGuess (source, modelTypeEXT);
+    PS_ASSERT (EXT, NULL);
+
+    // if (isnan(EXT->params->data.F32[1])) psAbort("nan in ext fit");
+
+    psphotCheckRadiusEXT (readout, source, EXT);
+
+    if ((source->moments->Sx < 1e-3) || (source->moments->Sx < 1e-3)) {
+        psTrace ("psphot", 5, "problem source: moments: %f %f\n", source->moments->Sx, source->moments->Sy);
+    }
+
+    // fit EXT (not PSF) model (set/unset the pixel mask)
+    pmSourceFitModel (source, EXT, PM_SOURCE_FIT_EXT, maskVal);
+    return (EXT);
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceFreePixels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceFreePixels.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceFreePixels.c	(revision 22147)
@@ -0,0 +1,12 @@
+# include "psphotInternal.h"
+
+void psphotSourceFreePixels (psArray *sources) {
+
+    if (!sources) return;
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	pmSourceFreePixels (source);
+    }
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourcePlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourcePlots.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourcePlots.c	(revision 22147)
@@ -0,0 +1,162 @@
+# include "psphotInternal.h"
+
+bool psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status = false;
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 21;
+    int DY = 21;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;                         // starting corner of next box
+    int dY = 0;                         // height of row so far
+    int NX = 20*DX;                     // full width of output image
+    int NY = DY;                        // total height of output image so far
+
+    // first, examine the PSF and SAT stars to set output image size:
+    // - add stamp widths until we exceed output image width,
+    // - then start a new row offset by max height
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        keep |= (source->mode & PM_SOURCE_MODE_PSFSTAR);
+        keep |= (source->mode & PM_SOURCE_MODE_SATSTAR);
+        if (!keep) {
+	    psTrace ("psphot", 4, "not plotting star %d: %x", i, source->mode);
+	    continue;
+	}
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (dX + DX > NX) {
+            // too wide for the rest of this row
+            if (dX == 0) {
+                // alone on this row
+                NY += DY;
+                dX = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                NY += dY;
+                dX = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            dX += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    if (NY == 0) {
+	psWarning ("no PSF or SAT stars to plot? skipping.\n");
+	return false;
+    }
+
+    // allocate output image
+    psImage *outpos = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImage *outsub = psImageAlloc (NX, NY, PS_TYPE_F32);
+
+    psImageInit (outpos, 0.0);
+    psImageInit (outsub, 0.0);
+
+    int Xo = 0;                         // starting corner of next box
+    int Yo = 0;                         // starting corner of next box
+    dY = 0;                             // height of row so far
+
+    int nPSF = 0;
+    int nSAT = 0;
+    int kapa = 0;                       // file descriptor for plotting routine
+
+    // first, examine the PSF and SAT stars:
+    // - generate radial plots (PS plots)
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+            nPSF ++;
+            keep = true;
+        }
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+            nSAT ++;
+            keep = true;
+        }
+        if (!keep) continue;
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (Xo + DX > NX) {
+            // too wide for the rest of this row
+            if (Xo == 0) {
+                // place source alone on this row
+                psphotAddWithTest (source, true, maskVal); // replace source if subtracted
+                psphotRadialPlot (&kapa, "radial.plots.ps", source);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+                psphotSubWithTest (source, false, maskVal); // remove source (force)
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+
+                psphotSetState (source, false, maskVal); // replace source (has been subtracted)
+                Yo += DY;
+                Xo = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                Yo += dY;
+                Xo = 0;
+                psphotAddWithTest (source, true, maskVal); // replace source if subtracted
+                psphotRadialPlot (&kapa, "radial.plots.ps", source);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+                psphotSubWithTest (source, false, maskVal); // remove source (force)
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+                psphotSetState (source, false, maskVal); // replace source (has been subtracted)
+
+                Xo = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            psphotAddWithTest (source, true, maskVal); // replace source if subtracted
+            psphotRadialPlot (&kapa, "radial.plots.ps", source);
+            psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+            psphotSubWithTest (source, false, maskVal); // remove source (force)
+            psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+            psphotSetState (source, false, maskVal); // replace source (has been subtracted)
+
+            Xo += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    psphotSaveImage (NULL, outpos, "outpos.fits");
+    psphotSaveImage (NULL, outsub, "outsub.fits");
+    psLogMsg ("psphot", PS_LOG_INFO, "plotted %d sources (%d psf, %d sat): %f sec\n", nPSF + nSAT, nPSF, nSAT, psTimerMark ("psphot"));
+
+    psFree (outpos);
+    psFree (outsub);
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceSize.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceSize.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceSize.c	(revision 22147)
@@ -0,0 +1,168 @@
+# include "psphotInternal.h"
+# include <gsl/gsl_sf_gamma.h>
+
+// we need to call this function after sources have been fitted to the PSF model and
+// subtracted.  this function examines the 9 pixels in the 3x3 square containing the peak and
+// compares the observed flux to the model.
+
+bool psphotSourceSize (pmReadout *readout, psArray *sources, psMetadata *recipe, long first) {
+
+    bool status;
+
+    psTimerStart ("psphot");
+
+    float CR_NSIGMA_LIMIT = psMetadataLookupF32 (&status, recipe, "PSPHOT.CRNSIGMA.LIMIT");
+    assert (status);
+
+    // loop over all source
+    for (int i = first; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// skip source if it was already measured
+	if (isfinite(source->crNsigma)) continue;
+
+	source->crNsigma  = -1.0;
+	source->extNsigma = -1.0;
+
+	// source must have been subtracted
+	source->crNsigma  = -3.0;
+	if (!(source->mode & PM_SOURCE_MODE_SUBTRACTED)) continue;
+
+	psF32 **resid = source->pixels->data.F32;
+	psF32 **weight = source->weight->data.F32;
+	psU8 **mask = source->maskObj->data.U8;
+
+	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
+	source->crNsigma  = -4.0;
+	if (xPeak < 1) continue;
+	if (xPeak > source->pixels->numCols - 2) continue;
+	if (yPeak < 1) continue;
+	if (yPeak > source->pixels->numRows - 2) continue;
+
+	// XXX for now, just skip any sources with masked pixels
+	source->crNsigma  = -5.0;
+	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]) { keep &= false; }
+	    }
+	}
+	if (!keep) continue;
+
+	// XXX need to deal with edge peaks... and mask
+	float cX = 2*resid[yPeak][xPeak] - resid[yPeak+0][xPeak-1] - resid[yPeak+0][xPeak+1];
+	float cY = 2*resid[yPeak][xPeak] - resid[yPeak+1][xPeak+0] - resid[yPeak+1][xPeak+0];
+	float cL = 2*resid[yPeak][xPeak] - resid[yPeak-1][xPeak-1] - resid[yPeak+1][xPeak+1];
+	float cR = 2*resid[yPeak][xPeak] - resid[yPeak+1][xPeak-1] - resid[yPeak-1][xPeak+1];
+
+	float dcX = 4*weight[yPeak][xPeak] + weight[yPeak+0][xPeak-1] + weight[yPeak+0][xPeak+1];
+	float dcY = 4*weight[yPeak][xPeak] + weight[yPeak+1][xPeak+0] + weight[yPeak+1][xPeak+0];
+	float dcL = 4*weight[yPeak][xPeak] + weight[yPeak-1][xPeak-1] + weight[yPeak+1][xPeak+1];
+	float dcR = 4*weight[yPeak][xPeak] + weight[yPeak+1][xPeak-1] + weight[yPeak-1][xPeak+1];
+
+	float nX = cX / sqrt(dcX);
+	float nY = cY / sqrt(dcY);
+	float nL = cL / sqrt(dcL);
+	float nR = cR / sqrt(dcR);
+
+	// 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)
+	source->psfChisq = PS_SQR (nX) + PS_SQR (nY) + PS_SQR (nX) + PS_SQR (nR);
+
+	float fCR = 0.0;
+	float fEXT = 0.0;
+	int nCR = 0;
+	int nEXT = 0;
+	if (nX > 0.0) {
+	    fCR += nX;
+	    nCR ++;
+	} else {
+	    fEXT += nX;
+	    nEXT ++;
+	}
+	if (nY > 0.0) {
+	    fCR += nY;
+	    nCR ++;
+	} else {
+	    fEXT += nY;
+	    nEXT ++;
+	}
+	if (nL > 0.0) {
+	    fCR += nL;
+	    nCR ++;
+	} else {
+	    fEXT += nL;
+	    nEXT ++;
+	}
+	if (nR > 0.0) {
+	    fCR += nR;
+	    nCR ++;
+	} else {
+	    fEXT += nR;
+	    nEXT ++;
+	}
+	source->crNsigma  = (nCR > 0)  ? fCR / nCR : 0.0;
+	source->extNsigma = (nEXT > 0) ? fabs(fEXT) / nEXT : 0.0;
+	// NOTE: abs needed to make the Nsigma value positive 
+
+	if (!isfinite(source->crNsigma)) {
+	  fprintf (stderr, ".");
+	  source->crNsigma = -6.0;
+	}
+
+	if (source->crNsigma > CR_NSIGMA_LIMIT) {
+	  source->mode |= PM_SOURCE_MODE_CRLIMIT;
+	}
+    }
+
+    psLogMsg ("psphot.size", PS_LOG_INFO, "measure source sizes for %ld sources: %f sec\n", sources->n - first, psTimerMark ("psphot"));
+
+    return true;
+}
+
+
+// some possible classes:
+// all 4 curvatures are highly negative : CR
+// all 4 curvatures are highly positive : EXT
+
+// at least 2 are significantly negative, none are significantly positive : CR
+// at least 2 are significantly positive, none are significantly negative : EXT
+
+// any are significantly negative, some may be significantly positive : CR
+// any are significantly positive, none may be significantly positive : EXT
+	
+/* Nn  Np  No
+   4   0   0   CR_1
+   3   1   0   CR_1
+   3   0   1   CR_1
+   2   2   0   CR_2
+   2   1   1   CR_2
+   2   0   2   CR_2
+   1   3   0   CR_3
+   1   2   1   CR_3
+   1   1   2   CR_3
+   1   0   3   CR_3
+   0   4   0   EXT
+   0   3   1   EXT
+   0   2   2   EXT
+   0   1   3   PSF
+   0   0   4   PSF
+*/
+
+/* Alternatively, write a f(CR) = Sum(nX,etc if >0) */
+	  
+
+/* I can write the formal probability that the 4 measurements are consistent with a PSF
+ * based on how large the error values are (Nsigma -> erf -> P(Nsigma)).
+ * I should examine this value as a function of flux and also examine the distribution of
+ * the probabilities for the PSF sources.
+ */
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceStats.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSourceStats.c	(revision 22147)
@@ -0,0 +1,112 @@
+# include "psphotInternal.h"
+
+psArray *psphotSourceStats (pmReadout *readout, psMetadata *recipe, pmDetections *detections)
+{
+    bool     status  = false;
+    psArray *sources = NULL;
+    float BIG_RADIUS;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType mark = psMetadataLookupU8(&status, recipe, "MASK.MARK"); // Mask value for bad pixels
+    assert (mark);
+
+    // determine properties (sky, moments) of initial sources
+    float INNER    = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    if (!status) return NULL;
+    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    if (!status) return NULL;
+    float RADIUS   = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    if (!status) return NULL;
+    float MIN_SN   = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN");
+    if (!status) return NULL;
+    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
+    if (!status) return NULL;
+
+    psArray *peaks = detections->peaks;
+
+    sources = psArrayAllocEmpty (peaks->n);
+
+    int Nfail = 0;
+    int Nmoments = 0;
+    for (int i = 0; i < peaks->n; i++) {
+
+        // create a new source, add peak
+        pmSource *source = pmSourceAlloc();
+        source->peak = (pmPeak *)psMemIncrRefCounter(peaks->data[i]);
+
+        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
+        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+        if (!strcasecmp (breakPt, "PEAKS")) {
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            continue;
+        }
+
+        // skip faint sources
+        if (source->peak->SN < MIN_SN) {
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            continue;
+        }
+
+        // measure a local sky value
+        // XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+        status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, mark);
+        if (!status) {
+          psFree (source);
+          Nfail ++;
+          psErrorClear();
+          continue;
+        }
+
+        // measure the local sky variance (needed if noise is not sqrt(signal))
+        // XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+        status = pmSourceLocalSkyVariance (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, mark);
+        if (!status) {
+          psFree (source);
+          Nfail ++;
+          psErrorClear();
+          continue;
+        }
+
+        // measure basic source moments
+        status = pmSourceMoments (source, RADIUS);
+        if (status) {
+            // add to the source array
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            Nmoments ++;
+            continue;
+        }
+
+        // if no valid pixels, or massive swing, likely saturated source,
+        // try a much larger box
+        BIG_RADIUS = PS_MIN (INNER, 3*RADIUS);
+        psTrace ("psphot", 4, "retrying moments for %d, %d\n", source->peak->x, source->peak->y);
+        status = pmSourceMoments (source, BIG_RADIUS);
+        if (status) {
+            // add to the source array
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            Nmoments ++;
+            continue;
+        }
+
+        psFree (source);
+        Nfail ++;
+        psErrorClear();
+        continue;
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld sources, %d moments, %d failed: %f sec\n", sources->n, Nmoments, Nfail, psTimerMark ("psphot"));
+
+    return (sources);
+}
+
+// XXX EAM : filter out bad peaks (eg, no valid pixels available for sky)
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotStandAlone.h
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotStandAlone.h	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotStandAlone.h	(revision 22147)
@@ -0,0 +1,21 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#ifndef PSPHOT_STAND_ALONE_H
+#define PSPHOT_STAND_ALONE_H
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+// Top level functions
+pmConfig       *psphotArguments (int argc, char **argv);
+bool            psphotParseCamera (pmConfig *config);
+bool            psphotImageLoop (pmConfig *config);
+bool            psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile);
+void            psphotCleanup (pmConfig *config);
+psExit          psphotGetExitStatus ();
+
+#endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSubtractBackground.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSubtractBackground.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSubtractBackground.c	(revision 22147)
@@ -0,0 +1,112 @@
+# include "psphotInternal.h"
+static int npass = 0;
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotSubtractBackground (pmConfig *config, const pmFPAview *view, const char *filename) 
+{
+    bool status = true;
+    pmReadout *background = NULL;
+    pmReadout *backSub = NULL;
+
+    psTimerStart ("psphot");
+
+    // find the currently selected readout
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, filename);
+    pmFPA *inFPA = file->fpa;
+    pmReadout *readout = pmFPAviewThisReadout (view, inFPA);
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // find the currently selected readout
+    pmReadout *model = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKMDL");
+    assert (model);
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    psImageBinning *binning = psMetadataLookupPtr(&status, recipe, "PSPHOT.BACKGROUND.BINNING");
+    assert (binning);
+
+    // select background pixels, from output background file, or create
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (file) {
+        // we are using PSPHOT.BACKGND as an I/O file: select readout or create
+        if (file->mode == PM_FPA_MODE_INTERNAL) {
+            background = file->readout;
+        } else {
+            background = pmFPAviewThisReadout (view, file->fpa);
+        }
+        if (background == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            background = pmFPAviewThisReadout (view, file->fpa);
+            if ((image->numCols != background->image->numCols) || (image->numRows != background->image->numRows)) {
+                psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for background dimensions");
+                return false;
+            }
+        }
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", image->numCols, image->numRows, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    if (!psImageUnbin (background->image, model->image, binning)) {
+        psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for unbinning");
+        return false;
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build resampled image: %f sec\n", psTimerMark ("psphot"));
+
+    // back-sub image pixels, from output background file (don't create if not requested)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
+    if (file) {
+        // we are using PSPHOT.BACKSUB as an I/O file: select readout or create
+        backSub = pmFPAviewThisReadout (view, file->fpa);
+        if (backSub == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            backSub = pmFPAviewThisReadout (view, file->fpa);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "image.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+        sprintf (name, "back.%02d.fits", npass);
+        psphotSaveImage (NULL, background->image, name);
+        sprintf (name, "mask.%02d.fits", npass);
+        psphotSaveImage (NULL, mask, name);
+        sprintf (name, "backmdl.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // subtract the background model (save in backSub, if requested)
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+            image->data.F32[j][i] -= backData[j][i];
+            if (backSub) {
+                backSub->image->data.F32[j][i] = image->data.F32[j][i];
+            }
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backsub.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "subtracted background model: %f sec\n", psTimerMark ("psphot"));
+
+    // the pmReadout selected in this function are all view on entries in config->files
+
+    npass ++;
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSummaryPlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSummaryPlots.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotSummaryPlots.c	(revision 22147)
@@ -0,0 +1,280 @@
+# include "psphotInternal.h"
+
+// the top portion of this file defines plotting functions which use kapa for plotting.
+// if kapa is not available, these functions are defined in the bottom portion as stubs
+// which perform NOP and return false (XXX should this be true??)
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+
+# include <kapa.h>
+
+// plot the sx, sy moments plane (faint and bright sources)
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.MOMENT.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping moments plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating moments plot");
+
+    // XXX get the 'showWindow' option from the recipes somewhere
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    KapaResize (kapa, 500, 500);
+    KapaInitGraph (&graphdata);
+
+    // examine sources to set data range
+    graphdata.xmin = -0.05;
+    graphdata.ymin = -0.05;
+    graphdata.xmax = +2.05;
+    graphdata.ymax = +2.05;
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "&ss&h_x| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "&ss&h_y| (pixels)", KAPA_LABEL_YM);
+	       
+    psVector *xBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *xFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // construct the vectors
+    int nB = 0;
+    int nF = 0;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+    
+	xFaint->data.F32[nF] = source->moments->Sx;
+	yFaint->data.F32[nF] = source->moments->Sy;
+	nF++;
+    
+	// XXX make this a user-defined cutoff
+	if (source->moments->SN < 25) continue;
+
+	xBright->data.F32[nB] = source->moments->Sx;
+	yBright->data.F32[nB] = source->moments->Sy;
+	nB++;
+    }
+    xFaint->n = nF;
+    yFaint->n = nF;
+
+    xBright->n = nB;
+    yBright->n = nB;
+  
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nF, &graphdata);
+    KapaPlotVector (kapa, nF, xFaint->data.F32, "x");
+    KapaPlotVector (kapa, nF, yFaint->data.F32, "y");
+  
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nB, &graphdata);
+    KapaPlotVector (kapa, nB, xBright->data.F32, "x");
+    KapaPlotVector (kapa, nB, yBright->data.F32, "y");
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xBright);
+    psFree (yBright);
+    psFree (xFaint);
+    psFree (yFaint);
+
+    return true;
+}
+
+// plot the sx, sy, sxy as vector field, 
+// plot the PSF measured sx, sy, sxy as vector field
+// pull the sources from the config / file?
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.PSFMODEL.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping psf model plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating psf model plot");
+
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    // XXX make the aspect-ratio match the image
+    KapaResize (kapa, 800, 800);
+    KapaInitGraph (&graphdata);
+
+    psVector *xMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+
+    // construct the plot vectors
+    int nMNT = 0;
+    int nPSF = 0;
+    int nMIN = 0;
+    float dx = 0;
+    float dy = 0;
+    float scale = 10;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+	if (source->moments->SN < 25) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+    
+	pmModel *model = source->modelPSF;
+        if (model == NULL) continue;
+
+	psF32 *PAR = model->params->data.F32;
+
+	psEllipseMoments moments;
+	moments.x2 = source->moments->Sx;
+	moments.y2 = source->moments->Sy;
+	moments.xy = source->moments->Sxy;
+
+	psEllipseShape shape;
+	shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+	shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+	shape.sxy = PAR[PM_PAR_SXY];
+
+	// force the axis ratio to be < 20.0
+	psEllipseAxes axes_mnt = psEllipseMomentsToAxes (moments, 20.0);
+	psEllipseAxes axes_psf = psEllipseShapeToAxes (shape, 20.0);
+
+	// moments major axis
+	dx = scale*axes_mnt.major*cos(axes_mnt.theta);
+	dy = scale*axes_mnt.major*sin(axes_mnt.theta);
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] - dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] - dy;
+	nMNT++;
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] + dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] + dy;
+	nMNT++;
+    
+	// psf major axis
+	dx = scale*axes_psf.major*cos(axes_psf.theta);
+	dy = scale*axes_psf.major*sin(axes_psf.theta);
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] - dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] - dy;
+	nPSF++;
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] + dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] + dy;
+	nPSF++;
+
+	// minor axis (to show size)
+	dy = +scale*axes_psf.minor*cos(axes_psf.theta);
+	dx = -scale*axes_psf.minor*sin(axes_psf.theta);
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] - dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] - dy;
+	nMIN++;
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] + dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] + dy;
+	nMIN++;
+
+	graphdata.xmin = PS_MIN(graphdata.xmin, PAR[PM_PAR_XPOS]);
+	graphdata.xmax = PS_MAX(graphdata.xmax, PAR[PM_PAR_XPOS]);
+	graphdata.ymin = PS_MIN(graphdata.ymin, PAR[PM_PAR_YPOS]);
+	graphdata.ymax = PS_MAX(graphdata.ymax, PAR[PM_PAR_YPOS]);
+    }
+    xMNT->n = yMNT->n = nMNT;
+    xPSF->n = yPSF->n = nPSF;
+    xMIN->n = yMIN->n = nMIN;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX set the plot range to match the image
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "x (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "y (pixels)", KAPA_LABEL_YM);
+    KapaSendLabel (kapa, "vector is major axis (scale by 20) : black are moments, blue are psf model, red is psf minor axis", KAPA_LABEL_XP);
+	       
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 100;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMNT, &graphdata);
+    KapaPlotVector (kapa, nMNT, xMNT->data.F32, "x");
+    KapaPlotVector (kapa, nMNT, yMNT->data.F32, "y");
+  
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nPSF, &graphdata);
+    KapaPlotVector (kapa, nPSF, xPSF->data.F32, "x");
+    KapaPlotVector (kapa, nPSF, yPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMIN, &graphdata);
+    KapaPlotVector (kapa, nMIN, xMIN->data.F32, "x");
+    KapaPlotVector (kapa, nMIN, yMIN->data.F32, "y");
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xMNT);
+    psFree (yMNT);
+    psFree (xPSF);
+    psFree (yPSF);
+    psFree (xMIN);
+    psFree (yMIN);
+
+    return true;
+}
+
+# else
+
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping moments plot");
+    return true;
+}
+
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping psf model plot");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTest.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTest.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTest.c	(revision 22147)
@@ -0,0 +1,83 @@
+# include "psphotInternal.h"
+
+void psExit (int status, char *process, char *format, ...) {
+
+    va_list ap;
+
+    va_start (ap, format);
+    fprintf (stderr, "exiting %s\n", process);
+    vfprintf (stderr, format, ap);
+    va_end (ap);
+
+    exit (status);
+}
+
+int main (int argc, char **argv) {
+
+    psRegion region = {0,0,0,0};        // a region representing the entire array
+    psphotTestArguments (&argc, argv);
+
+    psFits *file = psFitsOpen (argv[1], "r");
+    psMetadata *header = psFitsReadHeader (NULL, file);
+    psImage *image = psFitsReadImage (NULL, file, region, 0);
+    psFitsClose (file);
+
+    psImageJpegColormap (argv[5]);
+
+    // psImage *fimage = psImageCopy (NULL, image, PS_TYPE_F32);
+
+    int binning = atof(argv[6]);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN);
+    psImage *fimage = psImageRebin (NULL, image, NULL, 0, binning, stats);
+
+    float min = atof(argv[3]);
+    float max = atof(argv[4]);
+
+    psImageJpeg (fimage, argv[2], min, max);
+
+    psFree (header);
+    psFree (image);
+    exit (0);
+}
+
+
+# if (0)
+
+    psMetadata *row;
+    psArray *table;
+
+    psMetadataItem *mdi;
+
+    psMetadataConfigWrite (header, argv[2]);
+
+    // attempt to write image with NAXIS = 0
+    mdi = psMetadataLookup (header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    // create a test image
+    // psImage *tmpimage = psImageAlloc (10, 10, PS_DATA_F32);
+
+    // create a test table
+    table = psArrayAllocEmpty (10);
+
+    for (int i = 0; i < 10; i++) {
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "ROW",   PS_DATA_S32,    "", i);
+        psMetadataAdd (row, PS_LIST_TAIL, "FROW",  PS_TYPE_F32,    "", 0.1*i);
+        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY", PS_DATA_STRING, "", "test line");
+
+        table->data[i] = row;
+    }
+    table->n = 10;
+
+    psMetadata *theader = psMetadataAlloc ();
+    psMetadataAdd (theader, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING, "extension name", "SMPFILE");
+
+    psFits *fits = psFitsOpen (argv[3], "w");
+    // psFitsWriteImage (fits, header, tmpimage, 0);
+    psFitsWriteHeader (header, fits);
+    psFitsWriteTable (fits, theader, table);
+
+# endif
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestArguments.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestArguments.c	(revision 22147)
@@ -0,0 +1,19 @@
+# include "psphotInternal.h"
+static int usage ();
+
+void psphotTestArguments (int *argc, char **argv) {
+
+  // basic pslib options
+  psLogSetFormat ("M");
+  psArgumentVerbosity (argc, argv);
+
+  if (*argc != 7) usage ();
+
+  return;
+}
+
+static int usage () {
+
+    fprintf (stderr, "USAGE: psphotTest (input.fits) (output.jpg) (zero) (scale) (colormap) (rebin)\n");
+    exit (2);
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestPSF.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestPSF.c	(revision 22147)
@@ -0,0 +1,131 @@
+# include "psphotInternal.h"
+
+bool psphotTestPSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool            status;
+    char           *modelName;
+    pmPSF          *psf = NULL;
+    psArray        *stars = NULL;
+
+    psTimerStart ("psphot");
+
+    psphotSaveImage (NULL, readout->image,  "image.fits");
+
+    // check if a PSF model is supplied by the user
+    psf = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.PSF");
+    if (psf != NULL) return psf;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    if (!status) {
+        NSTARS = PS_MIN (sources->n, 200);
+        psWarning("PSF_MAX_NSTARS is not set in the recipe --- defaulting to %d\n", NSTARS);
+    }
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) {
+        POISSON_ERRORS = true;
+        psWarning("POISSON_ERRORS is not set in the recipe --- defaulting to true.\n");
+    }
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // how to model the PSF variations across the field
+    // XXX make a default value?  or not?
+    psMetadata *md = psMetadataLookupMetadata (&status, recipe, "PSF.TREND.MASK");
+    psPolynomial2D *psfTrendMask;
+    if (!status || !md) {
+        psWarning("PSF.TREND.MASK is not set in the recipe --- defaulting to use zeroth order.\n");
+        psfTrendMask = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 0, 0);
+    } else {
+        psfTrendMask = psPolynomial2DfromMetadata (md);
+        if (!psfTrendMask) {
+            psError(PSPHOT_ERR_PSF, true, "Unable to construct polynomial from PSF.TREND.MASK in the recipe");
+            return false;
+        }
+    }
+
+    stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; (i < sources->n) && (stars->n < NSTARS); i++) {
+        pmSource *source = sources->data[i];
+        // if (source->mode & PM_SOURCE_MODE_PSFSTAR) psArrayAdd (stars, 200, source);
+        psArrayAdd (stars, 200, source);
+    }
+    psLogMsg ("psphot.pspsf", 4, "selected candidate %ld PSF objects\n", stars->n);
+
+    if (stars->n == 0) {
+        psError(PSPHOT_ERR_PSF, true, "Failed to find any PSF candidates");
+        return NULL;
+    }
+
+    // get the fixed PSF fit radius
+    // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    if (!status) {
+        psWarning("PSF_FIT_RADIUS is not set in the recipe --- defaulting to 20.0\n");
+        RADIUS = 20.0;
+    }
+
+    // for this test, require a single model
+    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+    if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+    if (mdi->type != PS_DATA_STRING) psAbort("choose a single PSF_MODEL");
+    modelName = mdi->data.V;
+
+    pmPSFtestModel (stars, modelName, RADIUS, POISSON_ERRORS, psfTrendMask);
+
+    psphotSaveImage (NULL, readout->image,  "resid.fits");
+    psphotSaveImage (NULL, readout->mask,   "mask.fits");
+    psphotSaveImage (NULL, readout->weight, "weight.fits");
+    
+    return true;
+}
+
+bool pmPSFtestModel (psArray *sources, char *modelName, float RADIUS, bool poissonErrors, psPolynomial2D *psfTrendMask)
+{
+    bool status;
+    float x;
+    float y;
+
+    pmModelType type = pmModelSetType (modelName);
+    pmPSF *psf = pmPSFAlloc (type, poissonErrors, psfTrendMask);
+    if (psf == NULL) psAbort("unknown model");
+
+    FILE *f = fopen ("params.dat", "w");
+
+    // stage 1:  fit an independent model (freeModel) to all sources
+    psTimerStart ("fit");
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel  *model  = pmSourceModelGuess (source, psf->type);
+        x = source->peak->x;
+        y = source->peak->y;
+
+        // set temporary object mask and fit object
+        // fit model as EXT, not PSF
+        psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PM_MASK_MARK);
+        status = pmSourceFitModel (source, model, PM_SOURCE_FIT_EXT);
+        psImageKeepCircle (source->mask, x, y, RADIUS, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+	// write fitted parameters to file
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_XPOS]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_YPOS]);
+
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXX]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SYY]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXY]);
+
+	fprintf (f, "%f %d\n", model->chisq, model->nIter);
+
+	// subtract model flux
+	pmModelSub (source->pixels, source->mask, model, PM_MODEL_OP_FULL);
+    }
+    fclose (f);
+    psLogMsg ("psphot.psftest", 4, "fit ext: %f sec for %ld sources\n", psTimerMark ("fit"), sources->n);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestSourceOutput.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestSourceOutput.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotTestSourceOutput.c	(revision 22147)
@@ -0,0 +1,157 @@
+# include "psphotInternal.h"
+
+enum {
+    PSPHOT_ADD_NONE = 0,
+    PSPHOT_ADD_MODEL = 1,
+    PSPHOT_ADD_R0 = 2,
+    PSPHOT_ADD_R1 = 4,
+};
+
+bool psphotAddModel(psImage *image,
+		    pmModel *model,
+		    int mode
+    )
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *params = model->params;
+    psS32 imageCol;
+    psS32 imageRow;
+    psF32 skyValue = params->data.F32[0];
+    psF32 pixelValue;
+    
+    float xCenter = model->params->data.F32[PM_PAR_XPOS];
+    float yCenter = model->params->data.F32[PM_PAR_YPOS];
+    float Io = model->params->data.F32[PM_PAR_I0];
+
+    int xBin = 1;
+    int yBin = 1;
+    float xResidCenter = 0.0;
+    float yResidCenter = 0.0;
+
+    psImageInterpolateOptions *Ro = NULL;
+    psImageInterpolateOptions *Rx = NULL;
+    psImageInterpolateOptions *Ry = NULL;
+    if (model->residuals && (mode & (PSPHOT_ADD_R0 | PSPHOT_ADD_R1))) {
+	Ro = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Ro, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+	Rx = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Rx, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+	Ry = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Ry, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+
+	xBin = model->residuals->xBin;
+	yBin = model->residuals->yBin;
+	xResidCenter = model->residuals->xCenter;
+	yResidCenter = model->residuals->yCenter;
+    }
+
+    for (psS32 iy = 0; iy < image->numRows; iy++) {
+        for (psS32 ix = 0; ix < image->numCols; ix++) {
+
+            // Convert i/j to image coord space:
+	    imageCol = ix + image->col0;
+	    imageRow = iy + image->row0;
+
+            x->data.F32[0] = (float) imageCol;
+            x->data.F32[1] = (float) imageRow;
+
+            // set the appropriate pixel value for this coordinate
+	    if (mode & PSPHOT_ADD_MODEL) {
+		pixelValue = model->modelFunc (NULL, params, x) - skyValue;
+	    } else {
+		pixelValue = 0.0;
+	    }
+
+	    // get the contribution from the residual model
+	    // XXX for a test, do this for all sources and all pixels
+	    if (Ro) {
+		// fractional image position
+		// this is wrong for the 'center' case
+		float ox = xBin*(ix + 0.5 + image->col0 - xCenter) + xResidCenter;
+		float oy = yBin*(iy + 0.5 + image->row0 - yCenter) + yResidCenter;
+
+		psU8 mflux = 0;
+		double Fo = 0.0;
+		double Fx = 0.0;
+		double Fy = 0.0;
+		psImageInterpolate (&Fo, NULL, &mflux, ox, oy, Ro);
+		psImageInterpolate (&Fx, NULL, &mflux, ox, oy, Rx);
+		psImageInterpolate (&Fy, NULL, &mflux, ox, oy, Ry);
+
+		if (!mflux && isfinite(Fo) && isfinite(Fx) && isfinite(Fy)) {
+		    if (mode & PSPHOT_ADD_R0) {
+			pixelValue += Io*Fo;
+		    }
+		    if (mode & PSPHOT_ADD_R1) {
+			pixelValue += Io*(xCenter*Fx + yCenter*Fy);
+		    }
+		}
+	    }
+	    image->data.F32[iy][ix] += pixelValue;
+        }
+    }
+    psFree(x);
+    psFree(Ro);
+    psFree(Rx);
+    psFree(Ry);
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return(true);
+}
+
+// construct an initial PSF model for each object 
+bool psphotTestSourceOutput (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    psImage *imMo = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    psImage *imR0 = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    psImage *imR1 = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    
+    // create template model
+    pmModel *modelRef = pmModelAlloc(psf->type);
+    modelRef->params->data.F32[PM_PAR_SKY] = 0;
+    modelRef->params->data.F32[PM_PAR_I0] = 1000;
+
+    int dx = 25;
+    int dy = 25;
+
+    // generate a grid of fake sources with amplitude 1000
+    for (int iy = 50; iy < imMo->numRows; iy += 100) {
+	for (int ix = 50; ix < imMo->numCols; ix += 100) {
+	    
+	    // assign the x and y coords to the image center
+	    modelRef->params->data.F32[PM_PAR_XPOS] = ix;
+	    modelRef->params->data.F32[PM_PAR_YPOS] = iy;
+	    
+	    // create modelPSF from this model
+	    pmModel *model = pmModelFromPSF (modelRef, psf);
+	    model->residuals = psf->residuals;
+
+	    // generate working image for this source
+	    psRegion region = {ix - dx, ix + dx, iy - dy, iy + dy};
+
+	    psImage *vM = psImageSubset (imMo, region);
+	    psImage *v0 = psImageSubset (imR0, region);
+	    psImage *v1 = psImageSubset (imR1, region);
+
+	    // we want to make one image o
+	    psphotAddModel (vM, model, PSPHOT_ADD_MODEL);
+	    psphotAddModel (v0, model, PSPHOT_ADD_R0);
+	    psphotAddModel (v1, model, PSPHOT_ADD_R1);
+	}
+    }
+
+    psphotSaveImage (NULL, imMo, "grid.Mo.fits");
+    psphotSaveImage (NULL, imR0, "grid.R0.fits");
+    psphotSaveImage (NULL, imR1, "grid.R1.fits");
+
+    exit (0);
+}
+
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotVersion.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotVersion.c	(revision 22147)
@@ -0,0 +1,40 @@
+# include "psphotInternal.h"
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+# endif
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString psphotVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString psphotVersionLong(void)
+{
+    psString version = psphotVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+
+# if (HAVE_KAPA)
+    psString ohanaVersion = psStringStripCVS (ohana_version(), "Name");
+    psString libdvoVersion = psStringStripCVS (libdvo_version(), "Name");
+
+    psStringAppend (&version, " with libkapa (ohana %s, libdvo: %s)", ohanaVersion, libdvoVersion);
+    psFree (ohanaVersion);
+    psFree (libdvoVersion);
+# else
+    psStringAppend (&version, " WITHOUT libkapa");
+# endif
+
+    psFree(tag);
+    return version;
+}
+
+// Defined by RHL; leaving for backwards compatibility.
+const char *psphotCVSName(void) {
+   return cvsTag;
+}
Index: /branches/eam_branches/eam_branch_20080229/psphot/src/psphotWeightBias.c
===================================================================
--- /branches/eam_branches/eam_branch_20080229/psphot/src/psphotWeightBias.c	(revision 22147)
+++ /branches/eam_branches/eam_branch_20080229/psphot/src/psphotWeightBias.c	(revision 22147)
@@ -0,0 +1,90 @@
+# include "psphotInternal.h"
+
+// select objects fitted with PSF model
+// re-fit all of them with the non-poisson errors
+// only allow the normalization to vary
+// XXX eventually, we should be able to do this with linear fitting...
+// XXX UNUSED
+bool psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    int Nfit = 0;
+    bool status;
+    psF32 *PARp, *PARc;
+    psF32 x, y;
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // set fitting method to use non-poisson errors (local sky error)
+    float SKY_SIG = psMetadataLookupF32 (&status, recipe, "SKY_SIG");
+    if (!status) {
+      SKY_SIG = 1.0;
+        psWarning("SKY_SIG is not set --- defaulting to %f\n", SKY_SIG);
+    }
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) {
+        POISSON_ERRORS = true;
+        psWarning("POISSON_ERRORS is not set in the recipe --- defaulting to true.\n");
+    }
+    pmSourceFitModelInit (15, 0.1, PS_SQR(SKY_SIG), POISSON_ERRORS);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    FILE *f = fopen ("bias.dat", "w");
+    if (f == NULL) psAbort("can't open output file");
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip lower-quality objects
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+        if (source->mode &  PM_SOURCE_MODE_POOR) continue;
+        if (source->mode &  PM_SOURCE_MODE_FAIL) continue;
+        if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+        if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        if (source->mode &  PM_SOURCE_MODE_SATSTAR) continue;
+
+        // if model is NULL, we don't have a starting guess
+        if (source->modelPSF == NULL) continue;
+
+        if (source->moments->x < AnalysisRegion.x0) continue;
+        if (source->moments->y < AnalysisRegion.y0) continue;
+        if (source->moments->x > AnalysisRegion.x1) continue;
+        if (source->moments->y > AnalysisRegion.y1) continue;
+
+        // replace object in image
+        pmModelAdd (source->pixels, source->mask, source->modelPSF, PM_MODEL_OP_FULL);
+
+        // make a temporary model (we don't keep the result of this analysis)
+        pmModel *PSF = pmModelCopy (source->modelPSF);
+
+        // extend source radius as needed
+        psphotCheckRadiusPSF (readout, source, PSF);
+
+        x = PSF->params->data.F32[2];
+        y = PSF->params->data.F32[3];
+
+        // fit PSF model (set/unset the pixel mask)
+        psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "OR", PM_MASK_MARK);
+        pmSourceFitModel (source, PSF, PM_SOURCE_FIT_NORM);
+        psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+        // re-subtract PSF for object, leave local sky
+        pmModelSub (source->pixels, source->mask, source->modelPSF, PM_MODEL_OP_FULL);
+
+        PARp = source->modelPSF->params->data.F32;
+        PARc = PSF->params->data.F32;
+        fprintf (f, "%7.1f %7.1f %9.2f %9.2f %10.3f\n", PARp[2], PARp[3], PARp[1], PARc[1], source->moments->dSky);
+        Nfit ++;
+    }
+    fclose (f);
+    psLogMsg ("psphot", 3, "measure PSF weighting bias for %d objects: %f sec\n", Nfit, psTimerMark ("psphot"));
+
+    return (true);
+}
