Index: /tags/eam_tags/psphot_dev_09/psphot/.cvsignore
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/.cvsignore	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/.cvsignore	(revision 22342)
@@ -0,0 +1,16 @@
+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
Index: /tags/eam_tags/psphot_dev_09/psphot/Makefile.am
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/Makefile.am	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/Makefile.am	(revision 22342)
@@ -0,0 +1,5 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+EXTRA_DIST = autogen.sh
Index: /tags/eam_tags/psphot_dev_09/psphot/autogen.sh
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/autogen.sh	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/autogen.sh	(revision 22342)
@@ -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
+# 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: /tags/eam_tags/psphot_dev_09/psphot/configure.ac
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/configure.ac	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/configure.ac	(revision 22342)
@@ -0,0 +1,47 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.59)
+
+AC_INIT([psphot], [0.0.1], [http://pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+dnl AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+dnl handle debug building
+AC_ARG_ENABLE(optimize,
+  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="-O2"],
+  [AC_MSG_RESULT([compile optimization disabled])
+   CFLAGS="-O0 -g"]
+)
+
+dnl handle profiler building
+AC_ARG_ENABLE(profile,
+  [AS_HELP_STRING(--enable-profile,enable compiler profiler information inclusion)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -pg"]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,[],[AC_MSG_ERROR([jpeg library not found.])])
+PKG_CHECK_MODULES(PSLIB, pslib >= 0.9.0)
+PKG_CHECK_MODULES(PSMODULE, psmodule >= 0.0.0)
+
+dnl Set CFLAGS for build
+CFLAGS="${CFLAGS}"
+psphot_CFLAGS="-Wall -Werror -std=c99"
+AC_SUBST([psphot_CFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+
+AC_OUTPUT
Index: /tags/eam_tags/psphot_dev_09/psphot/doc/config.txt
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/doc/config.txt	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/doc/config.txt	(revision 22342)
@@ -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: /tags/eam_tags/psphot_dev_09/psphot/doc/notes.txt
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/doc/notes.txt	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/doc/notes.txt	(revision 22342)
@@ -0,0 +1,339 @@
+
+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: /tags/eam_tags/psphot_dev_09/psphot/doc/output.txt
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/doc/output.txt	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/doc/output.txt	(revision 22342)
@@ -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: /tags/eam_tags/psphot_dev_09/psphot/doc/psphot.txt
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/doc/psphot.txt	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/doc/psphot.txt	(revision 22342)
@@ -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: /tags/eam_tags/psphot_dev_09/psphot/doc/regions.txt
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/doc/regions.txt	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/doc/regions.txt	(revision 22342)
@@ -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: /tags/eam_tags/psphot_dev_09/psphot/doc/speed.txt
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/doc/speed.txt	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/doc/speed.txt	(revision 22342)
@@ -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: /tags/eam_tags/psphot_dev_09/psphot/doc/versions.txt
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/doc/versions.txt	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/doc/versions.txt	(revision 22342)
@@ -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: /tags/eam_tags/psphot_dev_09/psphot/src/.cvsignore
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/.cvsignore	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/.cvsignore	(revision 22342)
@@ -0,0 +1,9 @@
+*.o
+*.lo
+.libs
+.deps
+Makefile
+Makefile.in
+libpsphot.la
+psphot
+psphotTest
Index: /tags/eam_tags/psphot_dev_09/psphot/src/Makefile.am
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/Makefile.am	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/Makefile.am	(revision 22342)
@@ -0,0 +1,69 @@
+# lib_LTLIBRARIES = libpsphot.la
+# libpsphot_la_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(psphot_CFLAGS)
+# libpsphot_la_LDFLAGS = $(PSMODULE_LIBS) $(PSLIB_LIBS)
+
+bin_PROGRAMS = psphot
+psphot_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(psphot_CFLAGS)
+psphot_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS)
+
+# psphot_CPPFLAGS = $(libpsphot_la_CPPFLAGS)
+# psphot_LDFLAGS =  $(PSMODULE_LIBS) $(PSLIB_LIBS)
+# psphot_LDADD = libpsphot.la
+
+psphot_SOURCES =		\
+	psphotReadout.c		\
+	psphotImageStats.c	\
+	psphotImageBackground.c \
+	psphotFindPeaks.c	\
+	psphotSourceStats.c	\
+	psphotRoughClass.c	\
+	psphotBasicDeblend.c	\
+	psphotChoosePSF.c	\
+	psphotEnsemblePSF.c	\
+	psphotBlendFit.c	\
+	psphotReplaceUnfit.c	\
+	psphotApResid.c		\
+	psphotOutput.c		\
+	psphotLoadPixels.c	\
+	psphotModelGroupInit.c	\
+	psphotGrowthCurve.c	\
+	psphotCleanup.c	        \
+	psphotEvalPSF.c		\
+	psphotEvalFLT.c		\
+	psphotSourceFits.c	\
+	psphotSortBySN.c	\
+	psphotDefinePixels.c	\
+	psphotMagnitudes.c	\
+	psphotRadiusChecks.c	\
+	psphotImageMedian.c	\
+	psphotSkyReplace.c	\
+	psLine.c		\
+	psSparse.c		\
+	psPolynomialUtils.c	\
+	psModulesUtils.c	\
+	pmSourceContour.c	\
+	pmModelFitSet.c		\
+	pmCellSetMask.c		\
+	pmSourceFitSet.c     	\
+	psphot.c		\
+	psphotArguments.c	\
+	psphotParseCamera.c	\
+	psphotModelTest.c	\
+	psphotImageLoop.c	
+
+noinst_HEADERS =		\
+	psLibUtils.h		\
+	psModulesUtils.h	\
+	psphot.h		\
+	psSparse.h
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+
+#	psphotFullFit.c		
+#	psphotApplyPSF.c	
+#	psphotFitGalaxies.c
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_GAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_GAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_GAUSS.c	(revision 22342)
@@ -0,0 +1,152 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_GAUSS(psVector *deriv,
+			const psVector *params,
+			const psVector *x)
+{
+    psF32 X  = x->data.F32[0] - params->data.F32[2];
+    psF32 Y  = x->data.F32[1] - params->data.F32[3];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 r  = exp(-z);
+    psF32 q  = params->data.F32[1]*r;
+    psF32 f  = q + params->data.F32[0];
+
+    if (deriv != NULL) {
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+psF64 pmModelFlux_GAUSS(const psVector *params)
+{
+    psF64 A1   = PS_SQR(params->data.F32[4]);
+    psF64 A2   = PS_SQR(params->data.F32[5]);
+    psF64 A3   = PS_SQR(params->data.F32[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 pmModelRadius_GAUSS  (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);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+// define the parameter limits
+bool pmModelLimits_GAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_GAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+bool pmModelFromPSF_GAUSS (pmModel *modelPSF, pmModel *modelFLT, 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];
+
+    for (int i = 4; i < 7; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+// check the status of the fitted model
+bool pmModelFitStatus_GAUSS (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);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_PGAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_PGAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_PGAUSS.c	(revision 22342)
@@ -0,0 +1,167 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_PGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*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[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = PAR[1]*r
+        psF32 q = PAR[1]*r*r*t;
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+bool pmModelLimits_PGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+psF64 pmModelFlux_PGAUSS(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]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // 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
+// return the radius which yields the requested flux
+psF64 pmModelRadius_PGAUSS  (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);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelGuess_PGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+bool pmModelFromPSF_PGAUSS (pmModel *modelPSF, pmModel *modelFLT, 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];
+
+    for (int i = 4; i < 7; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_PGAUSS (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);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_QGAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_QGAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_QGAUSS.c	(revision 22342)
@@ -0,0 +1,197 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = 
+    params->data.F32[8] = 
+*****************************************************************************/
+
+/* XXX EAM : we need a way to have user-set values for fix parameters */
+# define QG_S1  2.5
+# define dQG_S1 1.5
+/* dQG_S1 is QG_S1 - 1.0 */
+
+psF32 pmModelFunc_QGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 r  = 1.0 / (1 + PAR[7]*z + pow(z, QG_S1));
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(PAR[7] + QG_S1*pow(z, dQG_S1));
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z;
+    }
+    return(f);
+}
+
+bool pmModelLimits_QGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.1;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+    params_max[0][0].data.F32[7] = 10.0;
+
+    return (TRUE);
+}
+
+bool pmModelGuess_QGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    params[7] = 1.0;
+    return(true);
+}
+
+psF64 pmModelFlux_QGAUSS(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]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // 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 + PAR[7]*z + pow(z, QG_S1));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_QGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[4], 1.0 / PAR[5]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[1];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+	f = 1.0 / (1 + PAR[7]*z + pow(z, QG_S1));
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_QGAUSS (pmModel *modelPSF, pmModel *modelFLT, 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];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_QGAUSS (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);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_RGAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_RGAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_RGAUSS.c	(revision 22342)
@@ -0,0 +1,151 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z + z^Npow)
+
+    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] = Npow
+*****************************************************************************/
+
+psF64 psModelFunc_RGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    // psF32 FACT = 1 + 5*exp(-5*PAR[7]);
+    
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z + z*p);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(1 + PAR[7]*p);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -q*px*X*2;
+        deriv->data.F32[5] = -q*py*Y*2;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+
+        // deriv->data.F32[4] = -1.8*q*px*X*2;
+        // deriv->data.F32[5] = -1.8*q*py*Y*2;
+        // deriv->data.F32[6] = -1.5*q*X*Y;
+        // deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_RGAUSS(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]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // 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 + pow(z, PAR[7]));
+	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
+// return the radius which yields the requested flux
+psF64 psModelRadius_RGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f, p;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[4], 1.0 / PAR[5]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[1];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+	p = pow(z, PAR[7]);
+	f = 1.0 / (1 + z + p);
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_RGAUSS (psModel *model, psSource *source) {
+
+    psVector *params = model->params;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 2.0;
+    return(true);
+}
+
+bool psModelFromPSF_RGAUSS (psModel *modelPSF, psModel *modelFLT, 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];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_SGAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_SGAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_SGAUSS.c	(revision 22342)
@@ -0,0 +1,342 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + St*z^Ntide)
+
+    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] = Npow
+    params->data.F32[8] = St
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+psF64 psImageEllipseContour (EllipseAxes axes, double xc, double yc, psImage *image);
+psF64 p_psImageGetElementF64(psImage *a, int i, int j);
+
+psF32 pmModelFunc_SGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = PS_MAX((0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y), 1e-8);
+    // note that if z -> 0, dPAR[7] -> -inf
+    // also z^(PAR[7]-1) -> Inf 
+
+    psF32 pr = z*PAR[8];
+    psF32 pr3 = pr*pr*pr;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + pr*pr3);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(PAR[7]*p + 4*PAR[8]*pr3);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -2*t*log(z)*z*p;
+        deriv->data.F32[8] = -2*t*4*z*pr3;
+    }
+    return(f);
+}
+
+bool pmModelLimits_SGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (9, PS_TYPE_F32);
+    *params_min = psVectorAlloc (9, PS_TYPE_F32);
+    *params_max = psVectorAlloc (9, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+    beta_lim[0][0].data.F32[8] = 0.05;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.5;
+    params_min[0][0].data.F32[8] = 0.05;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +3.0;
+    params_max[0][0].data.F32[7] = 5.0;
+    params_max[0][0].data.F32[8] = 0.5;
+
+    return (TRUE);
+}
+
+// measure the flux for the elliptical contour
+psF64 psImageEllipseContour (EllipseAxes axes, double xc, double yc, psImage *image) {
+
+    double t, dt, ct, st, xo, yo, value;
+    int N, Nt, x, y;
+
+    // choose dt to uniformly divide contour, with ~1 pix spacing at most
+    dt = asin (1 / axes.minor);
+    Nt = (int)(2*M_PI / dt) + 1;
+    dt = 2*M_PI / Nt;
+
+    ct = cos(axes.theta);
+    st = sin(axes.theta);
+    xo = xc - image->col0;
+    yo = yc - image->row0;
+    
+    psVector *contour = psVectorAlloc (Nt, PS_TYPE_F32);
+    for (t = 0, N = 0; (t < 2*M_PI) && (N < Nt); t += dt) {
+	x = ct*axes.major*cos(t) + st*axes.minor*sin(t) + xo;
+	y = ct*axes.minor*sin(t) + st*axes.major*cos(t) + yo;
+	value = p_psImageGetElementF64(image, x, y);
+	if (isfinite(value)) {
+	    contour->data.F32[N] = value;
+	    N++;
+	}
+    }
+    contour->n = N;
+    // accept every pixel: double counting is not so problematic here...
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, contour, NULL, NULL, 0);
+    value = stats->sampleMedian;
+
+    psFree (stats);
+    psFree (contour);
+
+    return (value);
+}
+    
+bool pmModelGuess_SGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+# if (0)    
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+	params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+	params[7] = 0.5;
+    }
+# endif
+
+    params[7] = 1.8;
+    params[8] = 0.1;
+
+
+    return(true);
+}
+
+// XXX EAM : test version using flux contours to guess slope
+bool pmModelGuess_SGAUSS_HARD (pmModel *model, pmSource *source) {
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+    float f1, f2;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+	params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+	params[7] = 0.5;
+    }
+    params[8] = 0.1;
+
+    return(true);
+}
+
+psF64 pmModelFlux_SGAUSS(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]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+      psF32 pr = PAR[8]*z;
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_SGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = EllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+	z = SQ(r);
+	pr = PAR[8]*z;
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	if (f < limit) break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+	fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_SGAUSS (pmModel *modelPSF, pmModel *modelFLT, 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];
+
+    for (int i = 4; i < 9; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_SGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+    EllipseAxes axes;
+    EllipseShape shape;
+  
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = EllipseShapeToAxes (shape);
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP += PS_SQR(dPAR[7] / PAR[7]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+    status &= (fabs(PAR[8]) < 0.5);
+    status &= (dPAR[8] < 0.1);
+    status &= (axes.major > 1.41);
+    status &= (axes.minor > 1.41);
+    status &= ((axes.major / axes.minor) < 5.0);
+    status &= (PAR[7] > 0.5);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_TAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_TAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_TAUSS.c	(revision 22342)
@@ -0,0 +1,164 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_TAUSS(psVector *deriv,
+                        const psVector *params,
+                        const psVector *x)
+{
+    psF32 X  = x->data.F32[0] - params->data.F32[2];
+    psF32 Y  = x->data.F32[1] - params->data.F32[3];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 r  = exp(-z);
+    psF32 q  = params->data.F32[1]*r;
+    psF32 f  = q + params->data.F32[0];
+
+    if (deriv != NULL) {
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+psF64 pmModelFlux_TAUSS(const psVector *params)
+{
+    psF64 A1   = PS_SQR(params->data.F32[4]);
+    psF64 A2   = PS_SQR(params->data.F32[5]);
+    psF64 A3   = PS_SQR(params->data.F32[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 pmModelRadius_TAUSS  (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);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+// define the parameter limits
+bool pmModelLimits_TAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_TAUSS (pmModel *model, pmSource *source)
+{
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+bool pmModelFromPSF_TAUSS (pmModel *modelPSF, pmModel *modelFLT, 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];
+
+    for (int i = 4; i < 7; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        // XXX: be wary of a bug here.  EAM had his own version of psPolynomial2DEval().
+        // I think the reason was that hewas using nX=xOrder.  Not sure.  I changed this
+        // without verifying.
+        // out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+        out[i] = psPolynomial2DEval(poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+// check the status of the fitted model
+bool pmModelFitStatus_TAUSS (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);
+
+    if (status)
+        return true;
+    return false;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_TGAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_TGAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_TGAUSS.c	(revision 22342)
@@ -0,0 +1,200 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = 
+    params->data.F32[8] = 
+*****************************************************************************/
+
+/* XXX EAM : we need a way to have user-set values for fix parameters */
+# define TG_S  2.60
+# define dTG_S 1.60
+# define TRF 0.50
+/* dTG_S is TG_S - 1.0 */
+
+psF32 pmModelFunc_TGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+    psF32 er = exp(TRF*z);
+
+    psF32 r  = 1.0 / (er + PAR[7]*z + pow(z, TG_S));    // (1/R)
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+	psF32 t = PAR[1]*r*r;	                         // df/dR
+	psF32 q = t*(TRF*er + PAR[7] + TG_S*pow(z, dTG_S));  // (df/dR)(dR/dz)
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+	deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z;
+    }
+    return(f);
+}
+
+bool pmModelLimits_TGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.1;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+    params_max[0][0].data.F32[7] = 10.0;
+
+    return (TRUE);
+}
+
+bool pmModelGuess_TGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    params[7] = 1.0;
+    return(true);
+}
+
+psF64 pmModelFlux_TGAUSS(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]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        psF32 er = exp(TRF*z);
+	f = 1.0 / (er + PAR[7]*z + pow(z, TG_S));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_TGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[4], 1.0 / PAR[5]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[1];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+        psF32 er = exp(TRF*z);
+	f = 1.0 / (er + PAR[7]*z + pow(z, TG_S));
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_TGAUSS (pmModel *modelPSF, pmModel *modelFLT, 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];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_TGAUSS (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);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_WAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_WAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_WAUSS.c	(revision 22342)
@@ -0,0 +1,99 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF64 psModelFunc_WAUSS(psVector *deriv,
+			const psVector *params,
+			const psVector *x)
+{
+    psF32 X = x->data.F32[0] - params->data.F32[2];
+    psF32 Y = x->data.F32[1] - params->data.F32[2];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 t = 0.5*z*z*(1.0 + params->data.F32[8]*z/3.0);
+    psF32 r = 1.0 / (1.0 + z + params->data.F32[7]*t); /* exp (-Z) */
+    psF32 f = params->data.F32[1]*r + params->data.F32[0];
+
+    if (deriv != NULL) {
+        // note difference from gaussian: q = params->data.F32[1]*r
+        psF32 q = params->data.F32[1]*r*r*(1.0 + params->data.F32[7]*z*(1.0 + params->data.F32[8]*z/2.0));
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -100.0*params->data.F32[1]*r*r*t;
+        deriv->data.F32[8] = -100.0*params->data.F32[1]*r*r*params->data.F32[7]*(z*z*z)/6.0;
+        // The values of 100 dampen the swing of params->data.F32[7,8] */
+    }
+    return(f);
+}
+
+// this is probably wrong since it uses the gauss integral 2 pi sigma^2
+psF64 psModelFlux_WAUSS(const psVector *params)
+{
+    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
+    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
+    psF64 A3   = params->data.F32[6];
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 psModelRadius_WAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+bool psModelGuess_WAUSS (psModel *model, pmSource *source) {
+
+    psVector *params = model->params;
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = sqrt(2.0) / source->moments->Sx;
+    params->data.F32[5] = sqrt(2.0) / source->moments->Sy;
+    params->data.F32[6] = source->moments->Sxy;
+    // XXX: What are these?
+    // params->data.F32[7] = B2;
+    // params->data.F32[8] = B3;
+    return(true);
+}
+
+bool psModelFromPSF_WAUSS (psModel *modelPSF, psModel *modelFLT, 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];
+
+    for (int i = 4; i < 9; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_ZGAUSS.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_ZGAUSS.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/models/pmModel_ZGAUSS.c	(revision 22342)
@@ -0,0 +1,158 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + PAR8*z^4)
+
+    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] = Npow
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+# define PAR8 0.1
+
+psF64 psModelFunc_ZGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 pr = PAR8*z;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + SQ(SQ(pr)));
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(PAR[7]*p + 4*PAR8*pr*pr*pr);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -q*px*X;
+        deriv->data.F32[5] = -q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*log(z)*z*p;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_ZGAUSS(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]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    psF32 pr = PAR8*z;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_ZGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = EllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+	z = SQ(r);
+	pr = PAR8*z;
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	if (f < limit) break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+	fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_ZGAUSS (psModel *model, psSource *source) {
+
+    psVector *params = model->params;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 1.9;
+    return(true);
+}
+
+bool psModelFromPSF_ZGAUSS (psModel *modelPSF, psModel *modelFLT, 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];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/pmCellSetMask.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/pmCellSetMask.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/pmCellSetMask.c	(revision 22342)
@@ -0,0 +1,31 @@
+# include "psphot.h"
+
+// 2006.02.04 : no leaks
+bool pmCellSetMask (pmCell *cell, psMetadata *recipe) {
+    
+    bool status;
+
+    // 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);
+
+    // Set the pixels
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout of interest
+
+	// psRegionForImage creates a region for the subimage, not the parent
+	// XXX I propose that psRegionForImage treat the region as referring to the parent...
+	psRegion keep = psRegionForImage (readout->image, valid);
+
+	// psImageKeepRegion assumes the region refers to the parent coordinates
+	psImageKeepRegion (readout->mask, keep, "OR", PSPHOT_MASK_INVALID);
+    }
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/pmModelFitSet.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/pmModelFitSet.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/pmModelFitSet.c	(revision 22342)
@@ -0,0 +1,72 @@
+# include "psphot.h"
+
+// sky, p1.1, p1.2, p1.3,... p1.n, p2.1, p2.2, 
+// nPar = nSrc*(nOnePar - 1) + 1
+
+static pmModelFunc mFunc;
+static int nPar;
+static psVector *onePar;
+static psVector *oneDeriv;
+
+bool pmModelFitSetInit (pmModelType type) {
+
+    mFunc = pmModelFunc_GetFunction (type);
+    nPar  = pmModelParameterCount (type);
+
+    onePar = psVectorAlloc (nPar, PS_DATA_F32);
+    oneDeriv = psVectorAlloc (nPar, PS_DATA_F32);
+
+    return true;
+}
+
+void pmModelFitSetClear (void) {
+
+    psFree (onePar);
+    psFree (oneDeriv);
+    return;
+}
+
+psF32 pmModelFitSet(psVector *deriv,
+		    const psVector *params,
+		    const psVector *x)
+{
+
+    psF32 value;
+    psF32 model;
+
+    psF32 *PAR = onePar->data.F32;
+    psF32 *dPAR = oneDeriv->data.F32;
+
+    psF32 *pars = params->data.F32;
+    psF32 *dpars = (deriv == NULL) ? NULL : deriv->data.F32;
+
+    int nSrc = (params->n - 1) / (nPar - 1);
+
+    PAR[0] = model = pars[0];
+    for (int i = 0; i < nSrc; i++) {
+	int nOff = i*nPar - i;
+	for (int n = 1; n < nPar; n++) {
+	    PAR[n] = pars[nOff + n];
+	}
+	if (deriv == NULL) {
+	    value = mFunc (NULL, onePar, x);
+	} else {
+	    value = mFunc (oneDeriv, onePar, x);
+	    for (int n = 1; n < nPar; n++) {
+		dpars[nOff + n] = dPAR[n];
+	    }
+	}
+	model += value;
+    }
+    if (deriv != NULL) {
+	dpars[0] = dPAR[0]*2.0;
+    }
+    return (model);
+}
+
+/*
+i:         0           1               2 
+n:         1  2  3  4  5  6  1  2  3  4  5  6  1  2  3  4  5  6
+i*6 + n: 0 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18
+*/
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/pmSourceContour.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/pmSourceContour.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/pmSourceContour.c	(revision 22342)
@@ -0,0 +1,213 @@
+# include "psphot.h"
+
+/******************************************************************************
+findValue(source, level, row, col, dir): a private function which determines
+the column coordinate of the model function which has the value "level".  If
+dir equals 0, then you loop leftwards from the peak pixel, otherwise,
+rightwards.
+ 
+XXX: reverse order of row,col args?
+ 
+XXX: Input row/col are in image coords.
+ 
+XXX: The result is returned in image coords.
+*****************************************************************************/
+# define LEFT false
+# define RIGHT true
+
+// return the first coordinate at or below the threshold in the requested direction
+static int findContourNeg(
+    psImage *image, 
+    float threshold, 
+    int x, 
+    int y, 
+    bool right) 
+{
+
+    psTrace(__func__, 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = image->numCols - 1;
+    } else {
+        incr = -1;
+        lastColumn = 0;
+    }
+
+    subCol = x;
+
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value <= threshold) {
+            psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+// return the last coordinate at or below the threshold in the requested direction
+static int findContourPos(
+    psImage *image, 
+    float threshold, 
+    int x, 
+    int y, 
+    bool right, 
+    int xEnd) 
+{
+
+    psTrace(__func__, 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = PS_MIN (image->numCols - 1, xEnd);
+    } else {
+        incr = -1;
+        lastColumn = PS_MAX (0, xEnd);
+    }
+
+    subCol = x;
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value >= threshold) {
+            psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+	    if (subCol == x) {
+		return (subCol);
+	    } 
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+/******************************************************************************
+new implementation of source contour function
+*****************************************************************************/
+psArray *pmSourceContour_EAM (psImage *image, int xc, int yc, float threshold) {
+
+    int xR, yR, x0, x1, x0s, x1s;
+
+    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    int x = xc - image->col0;
+    int y = yc - image->row0;
+
+    // the requested point must be within the contour
+    if (image->data.F32[y][x] < threshold) return NULL;
+
+    // Ensure that the starting column is allowable.
+    if (x < 0) return NULL;
+    if (y < 0) return NULL;
+    if (x >= image->numCols) return NULL;
+    if (y >= image->numRows) return NULL;
+
+    // Allocate data for x/y pairs.
+    psVector *xVec = psVectorAlloc(100, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc(100, PS_TYPE_F32);
+
+    // First row: find the left and right end-points
+    int Npt = 0;
+
+    x0 = findContourNeg (image, threshold, x, y, LEFT);
+    x1 = findContourNeg (image, threshold, x, y, RIGHT);
+    xVec->data.F32[Npt + 0] = image->col0 + x0;
+    xVec->data.F32[Npt + 1] = image->col0 + x1;
+    yVec->data.F32[Npt + 0] = image->row0 + y;
+    yVec->data.F32[Npt + 1] = image->row0 + y;
+    Npt += 2;
+
+    x0s = x0;
+    x1s = x1;
+
+    // look for contour outline above row
+    xR = x0s;
+    yR = y + 1;
+    while (yR < image->numRows) {
+	if (image->data.F32[yR][xR] < threshold) {
+	    x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+	    if (x0 == x1) {
+		// fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+		goto pt1;
+	    }
+	    x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+	    x0--;
+	} else {
+	    x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+	    x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+	}
+	// fprintf (stderr, "pos: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+	Npt += 2;
+
+	if (Npt >= xVec->nalloc - 1) {
+	    psVectorRealloc (xVec, xVec->nalloc + 100);
+	    psVectorRealloc (yVec, yVec->nalloc + 100);
+	}
+	yR ++;
+	xR = x0;
+    }
+
+pt1:
+    // look for contour outline below row
+    xR = x0s;
+    x1 = x1s;
+    yR = y - 1;
+    while (yR >= 0) {
+	if (image->data.F32[yR][xR] < threshold) {
+	    x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+	    if (x0 == x1) {
+		// fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+		goto pt2;
+	    }
+	    x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+	    x0--;
+	} else {
+	    x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+	    x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+	}
+	// fprintf (stderr, "neg: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+	Npt += 2;
+
+	if (Npt >= xVec->nalloc - 1) {
+	    psVectorRealloc (xVec, xVec->nalloc + 100);
+	    psVectorRealloc (yVec, yVec->nalloc + 100);
+	}
+	yR --;
+    }
+pt2:
+    xVec->n = Npt;
+    yVec->n = Npt;
+
+    // fprintf (stderr, "done\n");
+    psArray *tmpArray = psArrayAlloc(2);
+    tmpArray->data[0] = (psPtr *) xVec;
+    tmpArray->data[1] = (psPtr *) yVec;
+    psTrace(__func__, 3, "---- %s() end ----\n", __func__);
+    return(tmpArray);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/pmSourceFitSet.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/pmSourceFitSet.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/pmSourceFitSet.c	(revision 22342)
@@ -0,0 +1,256 @@
+# include "psphot.h"
+
+bool psphotFitSet (pmSource *source, pmModel *oneModel, char *fitset, bool PSF) {
+
+    double x, y, Io;
+
+    FILE *f = fopen (fitset, "r");
+    if (f == NULL) return false;
+
+    psArray *modelSet = psArrayAlloc (16);
+    modelSet->n = 0;
+
+    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);
+    }
+
+    pmSourceFitSet (source, modelSet, PSF);
+
+    // 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];
+	pmSourceSubModel (source->pixels, source->mask, model, false, false);
+    
+	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->mask, "mask.fits");
+    return true;
+}
+
+# define PM_SOURCE_FIT_MODEL_NUM_ITERATIONS 15
+# define PM_SOURCE_FIT_MODEL_TOLERANCE 0.1
+
+bool pmSourceFitSet (pmSource *source,
+		     psArray *modelSet,
+		     const bool PSF)
+{
+    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    psBool fitStatus = true;
+    psBool onPic     = true;
+    psBool rc        = true;
+
+    // base values on first model
+    pmModel *model = modelSet->data[0];
+
+    // set the static variables
+    pmModelFitSetInit (model->type);
+
+    int nSrc = modelSet->n;
+    int nPar = model->params->n - 1;  // number of object parameters (excluding sky)
+
+    // define parameter vectors for source set
+    psVector *params = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    psVector *dparams = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+
+    pmModelLimits modelLimits = pmModelLimits_GetFunction (model->type);
+
+    // define limits for single-source model
+    psVector *oneDelta;
+    psVector *oneParMin;
+    psVector *oneParMax;
+    modelLimits (&oneDelta, &oneParMin, &oneParMax);
+
+    psMinConstrain *constrain = psMinConstrainAlloc();
+    constrain->paramDelta = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    constrain->paramMin = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    constrain->paramMax = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    constrain->paramMask = PSF ? psVectorAlloc (nSrc*nPar + 1, PS_TYPE_U8) : NULL;
+
+    // all but the sky are allowed to vary independently (subject to PSF)
+    // set the parameters from the multiple models
+    // also, set limits based on single-source limits
+    params->data.F32[0] = model->params->data.F32[0];
+    constrain->paramDelta->data.F32[0] = oneDelta->data.F32[0];
+    constrain->paramMin->data.F32[0]   = oneParMin->data.F32[0];
+    constrain->paramMax->data.F32[0]   = oneParMax->data.F32[0];
+    constrain->paramMask->data.U8[0]   = 0;
+    for (int i = 0; i < nSrc; i++) {
+      model = modelSet->data[i];
+      for (int n = 1; n < nPar + 1; n++) {
+	params->data.F32[i*nPar + n] = model->params->data.F32[n];
+	dparams->data.F32[i*nPar + n] = model->dparams->data.F32[n];
+	constrain->paramDelta->data.F32[i*nPar + n] = oneDelta->data.F32[n];
+	constrain->paramMin->data.F32[i*nPar + n]   = oneParMin->data.F32[n];
+	constrain->paramMax->data.F32[i*nPar + n]   = oneParMax->data.F32[n];
+	if (PSF) {
+	    constrain->paramMask->data.U8[i*nPar + n] = (n < 4) ? 0 : 1;
+	}
+      }
+    }
+    psFree (oneDelta);
+    psFree (oneParMin);
+    psFree (oneParMax);
+
+    if (psTraceGetLevel(__func__) >= 5) {
+	for (int i = 0; i < params->n; i++) {
+	    fprintf (stderr, "%d %f %d\n", i, params->data.F32[i], constrain->paramMask->data.U8[i]);
+	}
+    }
+
+    // PSF model only fits first 4 parameters, EXT model fits all
+    int nParams = PSF ? nSrc*3 + 1 : nSrc*nPar + 1;
+
+    // maximum number of valid pixels
+    psS32 nPix = source->pixels->numRows * source->pixels->numCols;
+
+    // construct the coordinate and value entries
+    psArray *x = psArrayAlloc(nPix);
+    psVector *y = psVectorAlloc(nPix, PS_TYPE_F32);
+    psVector *yErr = psVectorAlloc(nPix, PS_TYPE_F32);
+
+    nPix = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+	    // skip masked points
+            if (source->mask->data.U8[i][j]) {
+		continue;
+	    }
+	    // skip zero-weight points
+	    if (source->weight->data.F32[i][j] == 0) {
+		continue;
+	    } 
+	    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+	    // Convert i/j to image space:
+	    coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+	    coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+	    x->data[nPix] = (psPtr *) coord;
+	    y->data.F32[nPix] = source->pixels->data.F32[i][j];
+	    // psMinimizeLMChi2 takes wt = 1/dY^2
+	    yErr->data.F32[nPix] = 1.0 / source->weight->data.F32[i][j];
+	    nPix++;
+        }
+    }
+    if (nPix <  nParams + 1) {
+        psTrace (__func__, 4, "insufficient valid pixels\n");
+        psTrace(__func__, 3, "---- %s() end : fail pixels ----\n", __func__);
+        model->status = PM_MODEL_BADARGS;
+	psFree (x);
+	psFree (y);
+	psFree (yErr);
+	psFree (params);
+	psFree (dparams);
+	psFree(constrain->paramMask);
+	psFree(constrain->paramMin);
+	psFree(constrain->paramMax);
+	psFree(constrain->paramDelta);
+	psFree(constrain);
+        return(false);
+    }
+    x->n = nPix;
+    y->n = nPix;
+    yErr->n = nPix;
+
+    psMinimization *myMin = psMinimizationAlloc(PM_SOURCE_FIT_MODEL_NUM_ITERATIONS,
+                            PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+
+    psTrace (__func__, 5, "fitting function\n");
+    fitStatus = psMinimizeLMChi2(myMin, covar, params, constrain, x, y, yErr, pmModelFitSet);
+
+    // parameter errors from the covariance matrix
+    for (int i = 0; i < dparams->n; i++) {
+        if ((constrain->paramMask != NULL) && constrain->paramMask->data.U8[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F64[i][i]);
+    }
+
+    // get the Gauss-Newton distance for fixed model parameters
+    if (constrain->paramMask != NULL) {
+        psVector *delta = psVectorAlloc (params->n, PS_TYPE_F64);
+        psMinimizeGaussNewtonDelta(delta, params, NULL, x, y, yErr, pmModelFitSet);
+        for (int i = 0; i < dparams->n; i++) {
+            if (!constrain->paramMask->data.U8[i])
+                continue;
+            dparams->data.F32[i] = delta->data.F64[i];
+        }
+	psFree (delta);
+    }
+
+    // assign back the parameters to the models
+    for (int i = 0; i < nSrc; i++) {
+      model = modelSet->data[i];
+      model->params->data.F32[0] = params->data.F32[0];
+      for (int n = 1; n < nPar + 1; n++) {
+	  model->params->data.F32[n] = params->data.F32[i*nPar + n];
+	  model->dparams->data.F32[n] = dparams->data.F32[i*nPar + n];
+      }
+      // save the resulting chisq, nDOF, nIter
+      // these are not unique for any one source
+      model->chisq = myMin->value;
+      model->nIter = myMin->iter;
+      model->nDOF  = y->n - nParams;
+
+      // set the model success or failure status
+      model->status = fitStatus ? PM_MODEL_SUCCESS : PM_MODEL_NONCONVERGE;
+
+      // models can go insane: reject these
+      onPic &= (model->params->data.F32[2] >= source->pixels->col0);
+      onPic &= (model->params->data.F32[2] <  source->pixels->col0 + source->pixels->numCols);
+      onPic &= (model->params->data.F32[3] >= source->pixels->row0);
+      onPic &= (model->params->data.F32[3] <  source->pixels->row0 + source->pixels->numRows);
+      if (!onPic) {
+	  model->status = PM_MODEL_OFFIMAGE;
+      }
+    }
+
+    source->mode |= PM_SOURCE_FITTED;
+
+    psFree(x);
+    psFree(y);
+    psFree(yErr);
+    psFree(myMin);
+    psFree(covar);
+    psFree(constrain->paramMask);
+    psFree(constrain->paramMin);
+    psFree(constrain->paramMax);
+    psFree(constrain->paramDelta);
+    psFree(constrain);
+    psFree(params);
+    psFree(dparams);
+
+    // free static memory used by pmModelFitSet
+    pmModelFitSetClear ();
+
+    rc = (onPic && fitStatus);
+    psTrace (__func__, 5, "onPic: %d, fitStatus: %d, nIter: %d, chisq: %f, nDof: %d\n", onPic, fitStatus, model->nIter, model->chisq, model->nDOF);
+    psTrace(__func__, 3, "---- %s end : status %d ----\n", __func__, rc);
+    return(rc);
+}
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psImageData.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psImageData.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psImageData.c	(revision 22342)
@@ -0,0 +1,24 @@
+# include "psphot.h"
+
+static void eamReadoutFree (eamReadout *imdata) {
+
+  if (imdata == NULL) return;
+
+  psFree (imdata->image);
+  psFree (imdata->header);
+  psFree (imdata->weight);
+  psFree (imdata->mask);
+  return;
+}
+
+eamReadout *eamReadoutAlloc (psImage *image, psImage *weight, psImage *mask, psMetadata *header) {
+  
+    eamReadout *imdata = psAlloc(sizeof(eamReadout));
+    imdata->image = image;
+    imdata->header = header;
+    imdata->weight = weight;
+    imdata->mask = mask;
+
+    psMemSetDeallocator(imdata, (psFreeFunc) eamReadoutFree);
+    return (imdata);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psImageJpeg.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psImageJpeg.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psImageJpeg.c	(revision 22342)
@@ -0,0 +1,165 @@
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include "jpeglib.h"
+
+/* vectors to hold JPEG colormap */
+static psVector *colorMapR = NULL;
+static psVector *colorMapG = NULL;
+static psVector *colorMapB = NULL;
+
+# define RANGELIM(A)(PS_MAX(0,PS_MIN(255,(A))))
+
+# define SCALEVALUE(VALUE,ZERO,SCALE)(PS_MAX(0,PS_MIN(255,(SCALE*(VALUE-ZERO)))))
+
+bool psImageJpegColormap (char *name) {
+
+    colorMapR = psVectorRecycle (colorMapR, 256, PS_TYPE_U8);
+    colorMapG = psVectorRecycle (colorMapG, 256, PS_TYPE_U8);
+    colorMapB = psVectorRecycle (colorMapB, 256, PS_TYPE_U8);
+
+    /* grayscale */
+    if ((!strcasecmp (name, "grayscale")) || (!strcasecmp (name, "greyscale"))) {
+	for (int i = 0; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(i);
+	    colorMapG->data.U8[i] = RANGELIM(i);
+	    colorMapB->data.U8[i] = RANGELIM(i);
+	}
+	return true;
+    }    
+
+    /* -grayscale */
+    if ((!strcasecmp (name, "-grayscale")) || (!strcasecmp (name, "-greyscale"))) {
+	for (int i = 0; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(256 - i);
+	    colorMapG->data.U8[i] = RANGELIM(256 - i);
+	    colorMapB->data.U8[i] = RANGELIM(256 - i);
+	}
+	return true;
+    }    
+
+    /* rainbow */
+    if (!strcasecmp (name, "rainbow")) { 
+	int I1 = 0.25*colorMapR->n;
+	int I2 = 0.50*colorMapR->n;
+	int I3 = 0.75*colorMapR->n;
+	for (int i = 0; i < I1; i++) {  
+	    colorMapR->data.U8[i] = 0;
+	    colorMapG->data.U8[i] = 0;
+	    colorMapB->data.U8[i] = RANGELIM(4*i);
+	}
+	for (int i = I1; i < I2; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(4*(i - I1));
+	    colorMapG->data.U8[i] = 0;
+	    colorMapB->data.U8[i] = RANGELIM(4*(I2 - i));
+	}
+	for (int i = I2; i < I3; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = 4*(i - I2); 
+	    colorMapB->data.U8[i] = 0;
+	}
+	for (int i = I3; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = 255;
+	    colorMapB->data.U8[i] = RANGELIM(4*(i - I3));
+	}
+	return true;
+    }
+
+    /* heat */
+    if (!strcasecmp (name, "heat")) { 
+	int I1 = 0.25*colorMapR->n;
+	int I2 = 0.50*colorMapR->n;
+	int I3 = 0.75*colorMapR->n;
+	for (int i = 0; i < I1; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(2*i);
+	    colorMapG->data.U8[i] = 0;
+	    colorMapB->data.U8[i] = 0;
+	}
+	for (int i = I1; i < I2; i++) {  
+	    colorMapR->data.U8[i] = RANGELIM(2*i);
+	    colorMapG->data.U8[i] = RANGELIM(2*(i - I1));
+	    colorMapB->data.U8[i] = 0;
+	}
+	for (int i = I2; i < I3; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = RANGELIM(2*(i - I1)); 
+	    colorMapB->data.U8[i] = RANGELIM(2*(i - I2)); 
+	}
+	for (int i = I3; i < colorMapR->n; i++) {  
+	    colorMapR->data.U8[i] = 255;
+	    colorMapG->data.U8[i] = 255;
+	    colorMapB->data.U8[i] = RANGELIM(2*(i - I2));
+	}
+	return true;
+    }
+    return false;
+}
+
+bool psImageJpeg (psImage *image, char *filename, float min, float max) {
+
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+
+    int pixel;
+    JSAMPLE *jpegLine;			// Points to data for current line
+    JSAMPROW jpegLineList[1];		// pointer to JSAMPLE row[s]
+    JSAMPLE *outPix;
+
+    if (colorMapR == NULL) return false;
+    if (colorMapG == NULL) return false;
+    if (colorMapB == NULL) return false;
+
+    /* JPEG init calls */
+    cinfo.err = jpeg_std_error (&jerr);
+    jpeg_create_compress (&cinfo);
+
+    /* open file, prep for jpeg */
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	fprintf (stderr, "failed to open %s for output\n", filename);
+	return (TRUE);
+    }
+    jpeg_stdio_dest(&cinfo, f);
+
+    /* set up color jpeg buffers */
+    int quality = 75;
+    cinfo.image_width = image->numCols; // image width and height, in pixels
+    cinfo.image_height = image->numRows;
+    cinfo.input_components = 3;		        
+    cinfo.in_color_space = JCS_RGB; 	
+    jpeg_set_defaults (&cinfo);
+    jpeg_set_quality (&cinfo, quality, true); // limit to baseline-JPEG values
+    jpeg_start_compress (&cinfo, true);
+
+    jpegLine = psAlloc (3*image->numCols*sizeof(JSAMPLE));
+    jpegLineList[0] = jpegLine;
+
+    psU8 *Rpix = colorMapR->data.U8;
+    psU8 *Gpix = colorMapG->data.U8;
+    psU8 *Bpix = colorMapB->data.U8;
+
+    float zero = min;
+    float scale = 256.0/(max - min);
+
+    for (int j = 0; j < image->numRows; j++) {
+	psF32 *row = image->data.F32[j];
+
+	outPix = jpegLine;
+	for (int i = 0; i < image->numCols; i++, outPix += 3) {
+	    pixel = SCALEVALUE(row[i],zero,scale);
+	    outPix[0] = Rpix[pixel];
+	    outPix[1] = Gpix[pixel];
+	    outPix[2] = Bpix[pixel];
+	}
+	jpeg_write_scanlines (&cinfo, jpegLineList, 1);
+    }
+
+    jpeg_finish_compress (&cinfo);
+    fclose (f);
+    jpeg_destroy_compress (&cinfo);
+
+    psFree (jpegLine);
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psLibUtils.h
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psLibUtils.h	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psLibUtils.h	(revision 22342)
@@ -0,0 +1,18 @@
+
+# ifndef PS_LIB_UTILS
+# define PS_LIB_UTILS
+
+// structure to carry a dynamic string
+typedef struct {
+    int NLINE;
+    int Nline;
+    char *line;
+} psLine;
+
+// psLine functions -- keep out for now?
+psLine      *psLineAlloc (int Nline);
+bool	     psLineInit (psLine *line);
+bool	     psLineAdd (psLine *line, char *format, ...);
+int          psStringStrip (char *string);
+
+# endif
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psLine.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psLine.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psLine.c	(revision 22342)
@@ -0,0 +1,66 @@
+# include <pslib.h>
+# include "psLibUtils.h"
+
+static void psLineFree (psLine *line) {
+
+  if (line == NULL) return;
+
+  psFree (line->line);
+  return;
+}
+
+// allocate a psLine structrue
+psLine *psLineAlloc (int Nline) {
+
+  psLine *line;
+  line = psAlloc (sizeof(psLine));
+  psMemSetDeallocator(line, (psFreeFunc) psLineFree);
+
+  line->Nline = 0;
+  line->NLINE = Nline;
+  line->line = psAlloc (Nline);
+  return (line);
+}
+
+bool psLineInit (psLine *line) {
+  if (line == NULL) return (false);
+  line->Nline = 0;
+  return (true);
+}
+
+bool psLineAdd (psLine *line, char *format, ...) {
+
+    int Nchar;
+    va_list ap;
+
+    if (line == NULL) return (false);
+
+    int nMax = line->NLINE - line->Nline;
+
+    va_start (ap, format);
+    Nchar = vsnprintf (&line->line[line->Nline], nMax, format, ap);
+    line->Nline += PS_MIN (nMax - 1, Nchar);
+    va_end (ap);
+
+    if (Nchar >= nMax) return (false);
+    return (true);
+}
+
+#ifndef whitespace
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+#endif
+
+/* Strip whitespace from the start and end of STRING. */
+int psStringStrip (char *string) {
+
+  int i;
+
+  if (string == (char *) NULL) return (FALSE);
+
+  for (i = 0; whitespace (string[i]); i++);
+  if (i) memmove (string, string + i, strlen(string+i)+1);
+  for (i = strlen (string) - 1; (i > 0) && whitespace (string[i]); i--);
+  string[++i] = 0;
+  return (i);
+
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psModulesUtils.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psModulesUtils.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psModulesUtils.c	(revision 22342)
@@ -0,0 +1,313 @@
+# include "psModulesUtils.h"
+
+// extract config informatin from config data or from image header, if specified
+// XXX EAM : this function should be replaced with Paul's image/header/metadata load scheme
+psF32 pmConfigLookupF32 (bool *status, psMetadata *config, psMetadata *header, char *name) {
+
+    char *source;
+    char *keyword;
+    psF32 value;
+    psMetadataItem *item;
+
+    // look for the entry in the config collection
+    item = psMetadataLookup (config, name);
+    if (item == NULL) {
+	psLogMsg ("pmConfigLookupF32", 2, "no key %s in config data, trying as header keyword", name);
+	value = psMetadataLookupF32 (status, header, name);
+	if (*status == false) {
+	    psAbort ("pmConfigLookupF32", "no key %s in header", name);
+	}
+	*status = true;
+	return (value);
+    }	
+
+    // I'm either expecting a string, with the name "HD:keyword"...
+    if (item->type == PS_DATA_STRING) {
+	source = item->data.V;
+	if (!strncasecmp (source, "HD:", 3)) {
+	    keyword = &source[3]; 
+	    value = psMetadataLookupF32 (status, header, keyword);
+	    if (*status == false) {
+		psAbort ("pmConfigLookupF32", "no key %s in config", name);
+	    }
+	    *status = true;
+	    // psFree (item);
+	    return (value);
+	}	
+    }
+
+    //  ... or a value (F32?)
+    if (item->type == PS_DATA_F32) {
+	value = item->data.F32;
+	// psFree (item);
+	return (value);
+    }
+
+    *status = false;
+    psError(PS_ERR_UNKNOWN, true, "invalid item");
+    return (0);
+}
+
+bool pmModelFitStatus (pmModel *model) {
+
+    bool status;
+
+    pmModelFitStatusFunc statusFunc = pmModelFitStatusFunc_GetFunction (model->type);
+    status = statusFunc (model);
+
+    return (status);
+}
+
+pmModel *pmModelSelect (pmSource *source) {
+    switch (source->type) {
+      case PM_SOURCE_STAR:
+	return source->modelPSF;
+	
+      case PM_SOURCE_EXTENDED:
+	return source->modelEXT;
+	
+      default:
+	return NULL;
+    }
+    return NULL;
+}
+
+bool pmSourcePhotometry (float *fitMag, float *obsMag, pmModel *model, psImage *image, psImage *mask) {
+
+    float obsSum = 0;
+    float fitSum = 0;
+    float sky = model->params->data.F32[0];
+
+    *fitMag = 99.0;
+    *obsMag = 99.0;
+
+    pmModelFlux modelFluxFunc = pmModelFlux_GetFunction (model->type);
+    fitSum = modelFluxFunc (model->params);
+    if (fitSum <= 0) return false;
+    if (!isfinite(fitSum)) return false;
+    *fitMag = -2.5*log10(fitSum);
+
+    for (int ix = 0; ix < image->numCols; ix++) {
+	for (int iy = 0; iy < image->numRows; iy++) {
+	    if (mask->data.U8[iy][ix]) continue;
+	    obsSum += image->data.F32[iy][ix] - sky;
+	}
+    }
+    if (obsSum <= 0) return false;
+    *obsMag = -2.5*log10(obsSum);
+
+    return (true);
+} 
+
+// force the fitted sky to meet the source flux at outer radius 
+bool pmModelSkyOffset (pmModel *model, float x, float y, float radius) {
+
+    float flux;
+
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+    psVector *params = model->params;
+  
+    coord->data.F32[0] = x + radius;
+    coord->data.F32[1] = y;
+    flux = modelFunc (NULL, params, coord);
+    params->data.F32[0] = flux;
+
+    psFree (coord);
+
+    return true;
+}
+
+pmModel *pmModelCopy (pmModel *model) {
+
+    pmModel *new = pmModelAlloc (model->type);
+    
+    new->chisq  = model->chisq;
+    new->nDOF   = model->nDOF;
+    new->nIter  = model->nIter;
+    new->status = model->status;
+    new->radius = model->radius;
+
+    for (int i = 0; i < new->params->n; i++) {
+        new->params->data.F32[i]  = model->params->data.F32[i];
+        new->dparams->data.F32[i] = model->dparams->data.F32[i];
+    }
+    
+    return (new);
+}
+
+float pmSourceCrossProduct (pmSource *Mi, pmSource *Mj) {
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    float flux, wt;
+
+    psImage *Pi = Mi->pixels;
+    psImage *Pj = Mj->pixels;
+
+    psImage *Wi = Mi->weight;
+
+    psImage *Ti = Mi->mask;
+    psImage *Tj = Mj->mask;
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+    
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+    
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+	for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+	    if (Ti->data.U8[yi][xi]) continue;
+	    if (Tj->data.U8[yj][xj]) continue;
+	    wt = Wi->data.F32[yi][xi];
+	    if (wt > 0) {
+		flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
+	    } 
+	}
+    }
+    return (flux);
+}
+
+float pmSourceCrossWeight (pmSource *Mi, pmSource *Mj) {
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    float flux, wt;
+
+    psImage *Pi = Mi->pixels;
+    psImage *Pj = Mj->pixels;
+
+    psImage *Wi = Mi->weight;
+
+    psImage *Ti = Mi->mask;
+    psImage *Tj = Mj->mask;
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+    
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+    
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+	for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+	    if (Ti->data.U8[yi][xi]) continue;
+	    if (Tj->data.U8[yj][xj]) continue;
+	    wt = Wi->data.F32[yi][xi];
+	    if (wt > 0) {
+		flux += 1.0 / wt;
+	    } 
+	}
+    }
+    return (flux);
+}
+
+static void ppConfigFree (ppConfig *config) {
+
+  if (config == NULL) return;
+  psFree (config->site);
+  psFree (config->camera);
+  psFree (config->recipe);
+  psFree (config->arguments);
+  psFree (config->options);
+  psFree (config->database);
+  return;
+}
+
+// allocate a ppConfig structrue
+ppConfig *ppConfigAlloc (void) {
+
+  ppConfig *config = psAlloc (sizeof(ppConfig));
+  psMemSetDeallocator(config, (psFreeFunc) ppConfigFree);
+
+  config->site = NULL;
+  config->camera = NULL;
+  config->recipe = NULL;
+  config->arguments = NULL;
+  config->options = NULL;
+  config->database = NULL;
+
+  return (config);
+}
+
+static void ppFileFree (ppFile *file) {
+
+  if (file == NULL) return;
+  psFree (file->phu);
+  psFree (file->filename);
+  psFree (file->fpa);
+  psFitsClose (file->fits);
+  return;
+}
+
+// allocate a ppFile structrue
+ppFile *ppFileAlloc (void) {
+
+  ppFile *file = psAlloc (sizeof(ppFile));
+  psMemSetDeallocator(file, (psFreeFunc) ppFileFree);
+
+  file->filename = NULL;
+  file->fits = NULL;
+  file->phu = NULL;
+  file->fpa = NULL;
+
+  return (file);
+}
+
+psMetadata *pmReadoutGetHeader (pmReadout *readout) {
+
+    p_pmHDU *hdu = NULL;
+
+    pmCell *cell = readout->parent;	// cell containing this readout;
+    hdu = cell->hdu;			// The data unit, containing the weight and mask originals
+    if (hdu) return hdu->header;
+    
+    pmChip *chip = cell->parent;	// The parent chip
+    hdu = chip->hdu;			// The data unit, containing the weight and mask originals
+    if (hdu) return hdu->header;
+
+    pmFPA *fpa = chip->parent;		// The parent FPA
+    hdu = fpa->hdu;
+    if (hdu) return hdu->header;
+
+    if (fpa->phu) return fpa->phu;
+    
+    psError(PS_ERR_UNKNOWN, true, "Unable to find the HDU in the hierarchy!\n");
+    return NULL;
+}
+
+bool psRegionIsNaN (psRegion region) {
+
+    if (!isfinite(region.x0)) return true;
+    if (!isfinite(region.x1)) return true;
+    if (!isfinite(region.y0)) return true;
+    if (!isfinite(region.y1)) return true;
+    return false;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psModulesUtils.h
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psModulesUtils.h	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psModulesUtils.h	(revision 22342)
@@ -0,0 +1,54 @@
+
+# ifndef PS_MODULE_UTILS
+# define PS_MODULE_UTILS
+
+# include <strings.h>  // for strcasecmp
+# include <pslib.h>
+# include <pmObjects.h>
+# include <pmModelGroup.h>
+# include "psLibUtils.h"
+
+// How much of the FPA to load at a time
+typedef enum {
+    PP_LOAD_NONE,                       // Don't load anything
+    PP_LOAD_FPA,                        // Load the entire FPA at once
+    PP_LOAD_CHIP,                       // Load by chip
+    PP_LOAD_CELL,                       // Load by cell
+} ppImageLoadDepth;
+
+// Configuration data
+typedef struct {
+    psMetadata *site;                   // The site configuration
+    psMetadata *camera;                 // The camera configuration
+    psMetadata *recipe;                 // The recipe (i.e., specific setups)
+    psMetadata *arguments;              // Command-line arguments
+    psMetadata *options;                // Command-line recipe options
+    psDB       *database;               // Database handle
+} ppConfig;
+
+// A file to process
+typedef struct {
+    char *filename;                     // File name
+    psFits *fits;                       // The FITS file handle
+    psMetadata *phu;                    // The FITS header
+    pmFPA *fpa;                         // The FPA, with pixels and extensions
+} ppFile;
+
+// psModule extra utilities
+// bool	     pmSourceFitModel_EAM(pmSource *source, pmModel *model, const bool PSF);
+bool 	     pmModelFitStatus (pmModel *model);
+int	     pmSourceDophotType (pmSource *source);
+bool	     pmSourcePhotometry (float *fitMag, float *obsMag, pmModel *model, psImage *image, psImage *mask);
+bool         pmModelSkyOffset (pmModel *model, float x, float y, float radius);
+
+// unify with paul's image/header/metadata functions
+psF32        pmConfigLookupF32 (bool *status, psMetadata *config, psMetadata *header, char *name);
+pmModel     *pmModelSelect (pmSource *source);
+
+ppConfig    *ppConfigAlloc (void);
+ppFile      *ppFileAlloc (void);
+psMetadata  *pmReadoutGetHeader (pmReadout *readout);
+
+bool psRegionIsNaN (psRegion region);
+
+# endif
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psPolynomialUtils.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psPolynomialUtils.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psPolynomialUtils.c	(revision 22342)
@@ -0,0 +1,168 @@
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+
+psPolynomial4D *psVectorChiClipFitPolynomial4D(
+    psPolynomial4D *poly,
+    psStats *stats,
+    const psVector *mask,
+    psMaskType maskValue,
+    const psVector *f,
+    const psVector *fErr,
+    const psVector *x,
+    const psVector *y,
+    const psVector *z,
+    const psVector *t)
+{
+    PS_ASSERT_POLY_NON_NULL(poly, NULL);
+    PS_ASSERT_POLY_TYPE(poly, PS_POLYNOMIAL_ORD, NULL);
+    PS_ASSERT_PTR_NON_NULL(stats, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(f, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(f, NULL);
+    if (mask != NULL) {
+        PS_ASSERT_VECTORS_SIZE_EQUAL(f, mask, NULL);
+        PS_ASSERT_VECTOR_TYPE(mask, PS_TYPE_U8, NULL);
+    }
+    PS_ASSERT_VECTOR_NON_NULL(x, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, x, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(x, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(y, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, y, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(y, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(z, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, z, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(z, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(t, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, t, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(t, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(fErr, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fErr, mask, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(fErr, NULL);
+
+    // clipping range defined by min and max and/or clipSigma
+    float minClipSigma;
+    float maxClipSigma;
+    if (isfinite(stats->max)) {
+        maxClipSigma = +fabs(stats->max);
+    } else {
+        maxClipSigma = +fabs(stats->clipSigma);
+    }
+    if (isfinite(stats->min)) {
+        minClipSigma = -fabs(stats->min);
+    } else {
+        minClipSigma = -fabs(stats->clipSigma);
+    }
+    psVector *fit   = NULL;
+    psVector *resid = psVectorAlloc (x->n, PS_TYPE_F64);
+
+    // eventual expansion: user supplies one of various stats option pairs,
+    // eg (SAMPLE_MEAN | SAMPLE_STDEV) and the correct pair is used to
+    // evaluate the clipping sigma
+    // for now, for the SAMPLE_MEDIAN and SAMPLE_STDEV to be used
+    stats->options |= (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    for (int N = 0; N < stats->clipIter; N++) {
+        int Nkeep = 0;
+
+        poly = psVectorFitPolynomial4D (poly, mask, maskValue, f, fErr, x, y, z, t);
+        fit = psPolynomial4DEvalVector (poly, x, y, z, t);
+        resid = (psVector *) psBinaryOp (resid, (void *) f, "-", (void *) fit);
+
+        stats  = psVectorStats (stats, resid, NULL, mask, maskValue);
+        psTrace (".psphot.VectorClipFit", 5, "resid stats: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+        // set mask if pts are not valid
+        // we are masking out any point which is out of range
+        // recovery is not allowed with this scheme
+        for (int i = 0; i < resid->n; i++) {
+            if ((mask != NULL) && (mask->data.U8[i] & maskValue)) {
+                continue;
+            }
+	    float sigma = hypot (psVectorGet (fErr, i), stats->sampleStdev);
+            if (resid->data.F64[i] - stats->sampleMedian > sigma*maxClipSigma) {
+                if (mask != NULL) {
+                    mask->data.U8[i] |= 0x01;
+                }
+                continue;
+            }
+            if (resid->data.F64[i] - stats->sampleMedian < sigma*minClipSigma) {
+                if (mask != NULL) {
+                    mask->data.U8[i] |= 0x01;
+                }
+                continue;
+            }
+            Nkeep ++;
+        }
+
+        psTrace (".psphot.VectorClipFit", 4, "keeping %d of %d pts for fit\n",
+                 Nkeep, x->n);
+
+        stats->clippedNvalues = Nkeep;
+        psFree (fit);
+    }
+    // Free local temporary variables
+    psFree (resid);
+
+    if (poly == NULL) {
+        psError(PS_ERR_UNKNOWN, true, "Could not fit a polynomial to the data.  Returning NULL.\n");
+        return(NULL);
+    }
+    return(poly);
+}
+
+psPolynomial2D *psImageBicubeFit (psImage *image, int x, int y) {
+
+    int ix = x - image->col0;
+    int iy = y - image->row0;
+
+    psF32 *Fm = &image->data.F32[iy - 1][ix];
+    psF32 *Fo = &image->data.F32[iy + 0][ix];
+    psF32 *Fp = &image->data.F32[iy + 1][ix];
+
+    double Fxm = Fm[-1] + Fo[-1] + Fp[-1];
+    double Fxp = Fm[+1] + Fo[+1] + Fp[+1];
+    double Fym = Fm[-1] + Fm[+0] + Fm[+1];
+    double Fyp = Fp[-1] + Fp[+0] + Fp[+1];
+    double Foo = Fym + Fyp + Fo[-1] + Fo[+0] + Fo[+1];
+
+    psPolynomial2D *poly = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 2, 2);
+    poly->mask[2][2] = 1;
+    poly->mask[1][2] = 1;
+    poly->mask[2][1] = 1;
+
+    poly->coeff[0][0] = Foo*(5.0/9.0) - (Fxp + Fxm)/3.0 - (Fyp + Fym)/3.0 ;
+
+    poly->coeff[1][0] = (Fxp - Fxm)/6.0;
+    poly->coeff[0][1] = (Fyp - Fym)/6.0;
+    
+    poly->coeff[2][0] = (Fxp + Fxm)/2.0 - Foo/3.0;
+    poly->coeff[0][2] = (Fyp + Fym)/2.0 - Foo/3.0;
+    
+    poly->coeff[1][1] = (Fp[+1] + Fm[-1] - Fm[+1] - Fp[-1])/4.0;
+    
+    return (poly);
+}
+
+psPlane psImageBicubeMin (psPolynomial2D *poly) {
+
+    psPlane min;
+
+    min.xErr = min.yErr = 0;
+
+    double det = 4*poly->coeff[2][0]*poly->coeff[0][2] - PS_SQR(poly->coeff[1][1]);
+
+    min.x = (poly->coeff[1][1]*poly->coeff[0][1] - 2*poly->coeff[0][2]*poly->coeff[1][0]) / det;
+    min.y = (poly->coeff[1][1]*poly->coeff[1][0] - 2*poly->coeff[2][0]*poly->coeff[0][1]) / det;
+    return (min);
+}
+
+// may need to extend this to change the keyname in the copy
+bool psMetadataItemTransfer (psMetadata *out, psMetadata *in, char *key) {
+
+    psMetadataItem *item = psMetadataLookup (in, key);
+    if (item == NULL) return false;
+
+    psMetadataAddItem (out, item, PS_LIST_TAIL, PS_META_REPLACE);
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psSparse.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psSparse.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psSparse.c	(revision 22342)
@@ -0,0 +1,235 @@
+# include "psphot.h"
+
+void psSparseMatrixTest () {
+
+    // build a sparse matrix
+    psSparse *sparse = psSparseAlloc (3, 9);
+
+    psSparseMatrixElement (sparse, 0, 0, 3.0);
+    psSparseMatrixElement (sparse, 1, 1, 2.0);
+    psSparseMatrixElement (sparse, 2, 2, 1.0);
+
+    psSparseMatrixElement (sparse, 1, 0, 0.1);
+    psSparseMatrixElement (sparse, 2, 0, -0.1);
+
+    psSparseResort (sparse);
+    for (int i = 0; i < sparse->Nelem; i++) {
+	fprintf (stderr, "%d %d %f\n", 
+		 sparse->Si->data.S32[i],
+		 sparse->Sj->data.S32[i],
+		 sparse->Aij->data.F32[i]);
+    }
+
+    psVector *x = psVectorAlloc (3, PS_DATA_F32);
+    x->data.F32[0] = 3;
+    x->data.F32[1] = 5;
+    x->data.F32[2] = 7;
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    psVector *B = psSparseMatrixTimesVector (NULL, sparse, x);
+    fprintf (stderr, "B: %f %f %f\n", B->data.F32[0], B->data.F32[1], B->data.F32[2]);
+
+    sparse->Bfj->data.F32[0] = B->data.F32[0];
+    sparse->Bfj->data.F32[1] = B->data.F32[1];
+    sparse->Bfj->data.F32[2] = B->data.F32[2];
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = -1e8;
+    constraint.paramMax   = +1e8;
+    constraint.paramDelta = +1e8;
+
+    x = psSparseSolve (x, constraint, sparse, 0);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 1);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 2);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 3);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 4);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+    return;
+} 
+
+void psSparseResort (psSparse *sparse) {
+
+    int Nelem = sparse->Nelem;
+
+    psVector *index = psVectorSortIndex (NULL, sparse->Sj);
+    psVector *Aij = sparse->Aij;
+    psVector *Si = sparse->Si;
+    psVector *Sj = sparse->Sj;
+
+    // allocate new temporary vectors
+    psVector *tAij = psVectorAlloc (Nelem, PS_DATA_F32);
+    psVector *tSi  = psVectorAlloc (Nelem, PS_DATA_S32);
+    psVector *tSj  = psVectorAlloc (Nelem, PS_DATA_S32);
+    for (int i = 0; i < Nelem; i++) {
+	int j = index->data.U32[i];
+	tAij->data.F32[i] = Aij->data.F32[j];
+	tSi->data.S32[i]  = Si->data.S32[j];
+	tSj->data.S32[i]  = Sj->data.S32[j];
+    }
+    psFree (index);
+    psFree (Aij);
+    psFree (Si);
+    psFree (Sj);
+
+    sparse->Aij = tAij;
+    sparse->Si = tSi;
+    sparse->Sj = tSj;
+    return;
+}
+
+// user should only add elements above the diagonal, but we don't check this
+void psSparseMatrixElement (psSparse *sparse, int i, int j, float value) {
+
+    int k;
+
+    if (i < j) {
+	fprintf (stderr, "*** error: subdiagonal element ***\n");
+	return;
+    }
+
+    if (i == j) {
+	// add to the diagonal
+	sparse->Qii->data.F32[i] = value;
+
+	// check vectors lengths and extend if needed
+	if (sparse->Nelem >= sparse->Aij->nalloc) {
+	    psVectorRealloc (sparse->Aij, sparse->Aij->nalloc + 100);
+	    psVectorRealloc (sparse->Si,  sparse->Si->nalloc + 100);
+	    psVectorRealloc (sparse->Sj,  sparse->Sj->nalloc + 100);
+	}
+
+	k = sparse->Nelem;
+	sparse->Aij->data.F32[k] = value;
+	sparse->Si->data.S32[k]  = i;
+	sparse->Sj->data.S32[k]  = j;
+	
+	sparse->Nelem ++;
+	sparse->Aij->n ++;
+	sparse->Si->n ++;
+	sparse->Sj->n ++;
+    } else {
+	// check vectors lengths and extend if needed
+	if (sparse->Nelem >= sparse->Aij->nalloc - 1) {
+	    psVectorRealloc (sparse->Aij, sparse->Aij->nalloc + 100);
+	    psVectorRealloc (sparse->Si,  sparse->Si->nalloc + 100);
+	    psVectorRealloc (sparse->Sj,  sparse->Sj->nalloc + 100);
+	}
+
+	k = sparse->Nelem;
+	sparse->Aij->data.F32[k] = value;
+	sparse->Si->data.S32[k]  = i;
+	sparse->Sj->data.S32[k]  = j;
+	k++;
+
+	sparse->Aij->data.F32[k] = value;
+	sparse->Si->data.S32[k]  = j;
+	sparse->Sj->data.S32[k]  = i;
+	
+	sparse->Nelem  += 2;
+	sparse->Aij->n += 2;
+	sparse->Si->n  += 2;
+	sparse->Sj->n  += 2;
+    }
+    return;
+}
+
+void psSparseVectorElement (psSparse *sparse, int i, float value) {
+
+    sparse->Bfj->data.F32[i] = value;
+    return;
+}
+
+// multiple A * x
+psVector *psSparseMatrixTimesVector (psVector *output, psSparse *matrix, psVector *vector) {
+
+    int i, Nelem;
+    float F;
+
+    if (output == NULL) {
+	output = psVectorAlloc (vector->n, PS_DATA_F32);
+    }
+
+    Nelem = 0;
+    for (int j = 0; j < vector->n; j++) {
+	F = 0;
+	while (matrix->Sj->data.S32[Nelem] == j) {
+	    i = matrix->Si->data.S32[Nelem];
+	    F += vector->data.F32[i] * matrix->Aij->data.F32[Nelem];
+	    Nelem++;
+	}
+	output->data.F32[j] = F;
+    }
+    return (output);
+}
+
+psVector *psSparseSolve (psVector *guess, psSparseConstraint constraint, psSparse *sparse, int Niter) {
+
+    psF32 dG;
+
+    psVector *Qii = sparse->Qii;
+    psVector *Bfj = sparse->Bfj;
+
+    guess = psVectorCopy (guess, Bfj, PS_DATA_F32);
+
+    // temporary storage for intermediate results
+    psVector *dQ = psVectorAlloc (guess->n, PS_DATA_F32);
+
+    for (int j = 0; j < Niter; j++) {
+	dQ = psSparseMatrixTimesVector (dQ, sparse, guess);
+	for (int i = 0; i < dQ->n; i++) {
+	    dG = (dQ->data.F32[i] - Bfj->data.F32[i]) / Qii->data.F32[i];
+	    if (fabs (dG) > constraint.paramDelta) {
+		if (dG > 0) {
+		    dG = +constraint.paramDelta;
+		} else {
+		    dG = -constraint.paramDelta;
+		}
+	    }
+	    guess->data.F32[i] -= dG;
+	    guess->data.F32[i] = PS_MAX (guess->data.F32[i], constraint.paramMin);
+	    guess->data.F32[i] = PS_MIN (guess->data.F32[i], constraint.paramMax);
+	}
+    }
+    psFree (dQ);
+    return (guess);
+}
+
+static void psSparseFree (psSparse *sparse) {
+    if (sparse == NULL) return;
+    psFree (sparse->Aij);
+    psFree (sparse->Bfj);
+    psFree (sparse->Qii);
+    psFree (sparse->Si);
+    psFree (sparse->Sj);
+    return;
+}
+
+// allocate a sparse matrix container for Nrows, with Nelem slots allocated
+psSparse *psSparseAlloc (int Nrows, int Nelem) {
+
+    psSparse *sparse = (psSparse *) psAlloc (sizeof(psSparse));
+    sparse->Aij = psVectorAlloc (Nelem, PS_DATA_F32);
+    sparse->Si  = psVectorAlloc (Nelem, PS_DATA_S32);
+    sparse->Sj  = psVectorAlloc (Nelem, PS_DATA_S32);
+
+    sparse->Aij->n = 0;
+    sparse->Si->n  = 0;
+    sparse->Sj->n  = 0;
+    sparse->Nelem = 0;
+
+    sparse->Bfj = psVectorAlloc (Nrows, PS_DATA_F32);
+    sparse->Qii = psVectorAlloc (Nrows, PS_DATA_F32);
+
+    sparse->Nrows = Nrows;
+    psMemSetDeallocator(sparse, (psFreeFunc) psSparseFree);
+    return (sparse);
+}
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psSparse.h
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psSparse.h	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psSparse.h	(revision 22342)
@@ -0,0 +1,24 @@
+
+typedef struct {
+    double paramDelta;
+    double paramMin;
+    double paramMax;
+} psSparseConstraint;
+
+typedef struct {
+    psVector *Aij;
+    psVector *Bfj;
+    psVector *Qii;
+    psVector *Si;
+    psVector *Sj;
+    int Nelem;
+    int Nrows;
+} psSparse;
+
+void psSparseMatrixTest ();
+void psSparseResort (psSparse *sparse);
+void psSparseMatrixElement (psSparse *sparse, int i, int j, float value);
+void psSparseVectorElement (psSparse *sparse, int i, float value);
+psVector *psSparseMatrixTimesVector (psVector *output, psSparse *matrix, psVector *vector);
+psVector *psSparseSolve (psVector *guess, psSparseConstraint constraint, psSparse *sparse, int Niter);
+psSparse *psSparseAlloc (int Nrows, int Nelem);
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphot.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphot.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphot.c	(revision 22342)
@@ -0,0 +1,30 @@
+# include "psphot.h"
+
+// XXX need a better structure for handling optional sequences
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+
+    // load implementation-specific models
+    psphotModelGroupInit ();
+
+    // load command-line arguments, options, and system config data
+    ppConfig *config = psphotArguments (&argc, argv);
+
+    // load input data (config and images (signal, noise, mask)
+    ppFile *input = psphotParseCamera (config);
+
+    // check on output mode,format, setup basic filename, etc
+    psphotOutputPrep (input, config);
+
+    // call psphot for each readout
+    psphotImageLoop (input, config);
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psFree (input);
+    psFree (config);
+    psphotCleanup ();
+ 
+    exit (0);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphot.h
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphot.h	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphot.h	(revision 22342)
@@ -0,0 +1,144 @@
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include <pmObjects.h>
+# include <pmGrowthCurve.h>
+# include <pmConfig.h>
+# include <pmFPARead.h>
+# include <pmPSF.h>
+# include <pmPSFtry.h>
+# include <pmModelGroup.h>
+# include "psLibUtils.h"
+# include "psModulesUtils.h"
+# include "psSparse.h"
+# include "pmFPAConstruct.h"
+# include "pmConcepts.h"
+
+# define PSPHOT_RECIPE "PSPHOT"
+
+# define psMemCopy(A)(psMemIncrRefCounter((A)))
+
+// top-level psphot functions
+ppConfig       *psphotArguments (int *argc, char **argv);
+ppFile         *psphotParseCamera (ppConfig *config);
+bool            psphotImageLoop (ppFile *input, ppConfig *config);
+
+bool            psphotModelTest (pmReadout *readout, psMetadata *arguments, psMetadata *recipe);
+bool            psphotReadout (pmReadout *readout, psMetadata *config);
+bool            ppImageLoadPixels (ppFile *input, psDB *db, int chipNum, int cellNum);
+void            psphotCleanup (void);
+
+// psphotReadout functions
+psStats        *psphotImageStats (pmReadout *readout, psMetadata *config);
+psPolynomial2D *psphotImageBackground (pmReadout *readout, psMetadata *config, psStats *sky);
+psArray        *psphotFindPeaks (pmReadout *readout, psMetadata *config);
+psArray        *psphotSourceStats (pmReadout *readout, psMetadata *config, psArray *allpeaks);
+bool            psphotRoughClass (psArray *sources, psMetadata *config);
+bool            psphotBasicDeblend (psArray *sources, psMetadata *config);
+pmPSF          *psphotChoosePSF (psMetadata *config, psArray *sources);
+void            psphotOutput (pmReadout *readout, psMetadata *arguments);
+psImage        *psphotImageMedian (pmReadout *readout, psMetadata *config);
+
+// optional object analysis steps
+bool 	        psphotEnsemblePSF (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf, bool final);
+bool            psphotBlendFit (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf);
+bool            psphotReplaceUnfit (psArray *sources);
+bool            psphotApResid (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf);
+
+// XXX deprecate these?
+// bool            psphotFullFit (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky);
+// bool            psphotApplyPSF (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky);
+// bool            psphotFitExtended (pmReadout *readout, psMetadata *config, psArray *sources, psStats *skyStats);
+
+// basic support functions
+pmModel        *pmModelCopy (pmModel *model);
+pmModel        *pmSourceMagnitudes (pmSource *source, pmPSF *psf, float apRadius);
+float           pmSourceCrossProduct (pmSource *Mi, pmSource *Mj);
+float           pmSourceCrossWeight (pmSource *Mi, pmSource *Mj);
+psArray        *pmSourceContour_EAM (psImage *image, int x, int y, float threshold);
+void            psphotModelGroupInit (void);
+int 	        psphotSortBySN (const void **a, const void **b);
+int 	        psphotSortByY (const void **a, const void **b);
+bool            psphotGrowthCurve (pmReadout *readout, pmPSF *psf);
+void            psphotTestArguments (int *argc, char **argv);
+bool            pmCellSetMask (pmCell *cell, psMetadata *recipe);
+bool            psphotBackgroundNames (psMetadata *arguments);
+bool            psphotSkyReplace (pmReadout *readout, psImage *background);
+
+// functions to set the correct source pixels
+bool 		psphotInitRadiusPSF (psMetadata *config, pmModelType type);
+bool 		psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model);
+bool 		psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR);
+bool 		psphotInitRadiusEXT (psMetadata *config, pmModelType type);
+bool 		psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model);
+bool 	        psphotDefinePixels (pmSource *mySource, const pmReadout *readout, psF32 x, psF32 y, psF32 Radius);
+bool 	        psphotRedefinePixels (pmSource *mySource, const pmReadout *readout, psF32 x, psF32 y, psF32 Radius);
+
+// output functions
+bool 	     	pmSourcesWriteSX   (psArray *sources, char *filename);
+bool 	     	pmSourcesWriteOBJ  (psArray *sources, char *filename);
+bool 	     	pmSourcesWriteCMP  (psArray *sources, char *filename, psMetadata *header);
+bool 	     	pmSourcesWriteCMF  (psArray *sources, char *filename, psMetadata *header);
+bool 	     	pmSourcesWriteText (psArray *sources, char *filename);
+
+bool 	     	pmModelWritePSFs (psArray *sources, char *filename);
+bool 	     	pmModelWriteEXTs (psArray *sources, char *filename);
+bool 	     	pmModelWriteNULLs (psArray *sources, char *filename);
+bool 	     	pmPeaksWriteText (psArray *sources, char *filename);
+bool 	     	pmMomentsWriteText (psArray *sources, char *filename);
+
+bool            psphotSamplePSFs (pmPSF *psf, psImage *image, char *output);
+bool 		psphotDumpMoments (psMetadata *config, psArray *sources);
+int             psphotSaveImage (psMetadata *header, psImage *image, char *filename);
+bool            psphotUpdateHeader (psMetadata *header, psMetadata *config);
+int  	     	pmSourcesDophotType (pmSource *source);
+bool            psMetadataItemTransfer (psMetadata *out, psMetadata *in, char *key);
+
+char           *psphotSplitName (psMetadata *header);
+void            psphotOutputPrep (ppFile *file, ppConfig *config);
+void            psphotOutputCleanup (void);
+char           *psphotNameSubstitute (char *input, char *replace, char *key);
+
+bool            psphotMagnitudes (psMetadata *config, psArray *sources, pmPSF *psf);
+
+// 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 simultaneous multi-source fitting
+bool 		psphotFitSet (pmSource *oneSrc, pmModel *oneModel, char *fitset, bool PSF);
+bool 		pmSourceFitSet (pmSource *source, psArray *modelSet, const bool PSF);
+psF32           pmModelFitSet (psVector *deriv, const psVector *params, const psVector *x);
+bool            pmModelFitSetInit (pmModelType type);
+void            pmModelFitSetClear (void);
+
+//  functions to support the source fitting process
+bool 		psphotInitLimitsPSF (psMetadata *config);
+bool		psphotInitLimitsEXT (psMetadata *config);
+bool		psphotFitBlend (pmReadout *readout, pmSource *source);
+bool		psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources);
+bool		psphotFitPSF (pmReadout *readout, pmSource *source);
+pmModel	       *psphotFitEXT (pmReadout *readout, pmSource *source);
+psArray	       *psphotFitDBL (pmReadout *readout, pmSource *source);
+
+// bicubic interpolation
+psPolynomial2D *psImageBicubeFit (psImage *image, int x, int y);
+psPlane         psImageBicubeMin (psPolynomial2D *poly);
+
+bool psImageJpegColormap (char *name);
+bool psImageJpeg (psImage *image, char *filename, float zero, float scale);
+
+// optional mode for clip fit?
+psPolynomial4D *psVectorChiClipFitPolynomial4D(
+    psPolynomial4D *poly,
+    psStats *stats,
+    const psVector *mask,
+    psMaskType maskValue,
+    const psVector *f,
+    const psVector *fErr,
+    const psVector *x,
+    const psVector *y,
+    const psVector *z,
+    const psVector *t);
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotApResid.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotApResid.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotApResid.c	(revision 22342)
@@ -0,0 +1,271 @@
+# include "psphot.h"
+static char DEFAULT_OPTION[] = "SKYBIAS";
+
+// measure the aperture residual statistics
+bool psphotApResid (pmReadout *readout, psArray *sources, psMetadata *config, pmPSF *psf) { 
+
+    int Npsf;
+    bool status;
+    pmModel *model;
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    // S/N limit to perform full non-linear fits
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, config, "FULL_FIT_SN_LIM");
+
+    // measure the aperture loss as a function of radius for PSF
+    float REF_RADIUS = psMetadataLookupF32 (&status, config, "PSF_REF_RADIUS");
+    psf->growth = pmGrowthCurveAlloc (3.0, REF_RADIUS, 0.1);
+    psphotGrowthCurve (readout, psf);
+    
+    psVector *mask    = psVectorAlloc (300, PS_TYPE_U8);
+    psVector *xPos    = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *yPos    = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *flux    = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *r2rflux = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *apResid = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *dMag    = psVectorAlloc (300, PS_TYPE_F64);
+    mask->n = xPos->n = yPos->n = flux->n = r2rflux->n = apResid->n = dMag->n = 0;
+    Npsf = 0;
+
+    // select all good PM_SOURCE_STAR entries
+    for (int i = 0; i < sources->n; i++) {
+	source = sources->data[i];
+
+	if (source->type != PM_SOURCE_STAR) continue; 
+	if (source->mode &  PM_SOURCE_SATSTAR) continue;
+	if (source->mode &  PM_SOURCE_BLEND) continue;
+	if (source->mode &  PM_SOURCE_FAIL) continue;
+	if (source->mode &  PM_SOURCE_POOR) continue;
+
+	// XXX tune independently?
+	if (source->moments->SN < 2*FIT_SN_LIM) continue;
+
+	// get uncorrected magnitudes in scaled apertures
+	model = pmSourceMagnitudes (source, NULL, 0);
+	if (model == NULL) continue;
+
+	mask->data.U8[Npsf] = 0;
+	xPos->data.F64[Npsf] = model->params->data.F32[2];
+	yPos->data.F64[Npsf] = model->params->data.F32[3];
+
+	flux->data.F64[Npsf] = pow(10.0, -0.4*source->fitMag);
+	r2rflux->data.F64[Npsf] = PS_SQR(model->radius) / flux->data.F64[Npsf];
+	
+	apResid->data.F64[Npsf] = source->apMag + pmGrowthCurveCorrect (psf->growth, model->radius) - source->fitMag ;
+
+	// XXX sanity clip?
+	// XXX need to see if all data were tossed?
+	// XXX need to subtract median?
+	if (fabs(apResid->data.F64[Npsf]) > 0.2) continue;
+
+	dMag->data.F64[Npsf] = model->dparams->data.F32[1] / model->params->data.F32[1];
+
+	psVectorExtend (mask, 	 100, 1);
+	psVectorExtend (xPos, 	 100, 1);
+	psVectorExtend (yPos, 	 100, 1);
+	psVectorExtend (flux,    100, 1);
+	psVectorExtend (r2rflux, 100, 1);
+	psVectorExtend (dMag,    100, 1);
+	psVectorExtend (apResid, 100, 1);
+	Npsf ++;
+    }
+    psLogMsg ("psphot.apresid", 4, "measure aperture residuals : %f sec for %d objects\n", psTimerMark ("psphot"), Npsf);
+
+    // APTREND options : NONE SKYBIAS XY_LIN XY_QUAD SKY_XY_LIN FULL 
+    char *ApTrendOption = psMetadataLookupPtr (&status, config, "APTREND");
+    if (!status) ApTrendOption = DEFAULT_OPTION;
+
+    // 3hi/1lo sigma clipping on the rflux vs metric fit
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->min = 3.0;
+    stats->max = 3.0;
+
+    // constant only
+    if (!strcasecmp (ApTrendOption, "CONSTANT")) {
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+    // constant and skybias only
+    if (!strcasecmp (ApTrendOption, "SKYBIAS")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+    if (!strcasecmp (ApTrendOption, "SKYSAT")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_SKYSAT);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+    // constant and linear X,Y only
+    if (!strcasecmp (ApTrendOption, "XY_LIN")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_XY_LIN);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+    // constant and quadratic X,Y only
+    if (!strcasecmp (ApTrendOption, "XY_QUAD")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_XY_QUAD);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+     // constant and sky, linear X,Y only
+    if (!strcasecmp (ApTrendOption, "SKY_XY_LIN")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_SKY_XY_LIN);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+     // constant and sky, linear X,Y only
+    if (!strcasecmp (ApTrendOption, "SKYSAT_XY_LIN")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_SKYSAT_XY_LIN);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+    if (!strcasecmp (ApTrendOption, "ALL")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// fit just SkyBias and clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+
+	// finally, fit x, y, SkyBias and clip out objects which are too far from the median 
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf, PM_PSF_ALL);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+    }
+
+    // construct the fitted values and the residuals
+    psVector *fit   = psPolynomial4DEvalVector (psf->ApTrend, xPos, yPos, r2rflux, flux);
+    psVector *resid = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) fit);
+
+# if (0)
+    FILE *fout = fopen ("resid.dat", "w");
+    for (int i = 0; i < resid->n; i++) {
+	fprintf (fout, "%d %f %f %f %f  %f %f %f %f %d\n", 
+		 i, 
+		 (float) psVectorGet(xPos, i), (float) psVectorGet(yPos, i), (float) psVectorGet(r2rflux, i), (float) psVectorGet(flux, i), 
+		 (float) psVectorGet(apResid, i), (float) psVectorGet(fit, i), (float) psVectorGet(resid, i), (float) psVectorGet(dMag, i),
+		 mask->data.U8[i]);
+    }
+    fclose (fout);
+# endif
+
+    // measure scatter for sources with dMag < 0.01 (S/N = 100)
+    psStats *residStats = psStatsAlloc (PS_STAT_SAMPLE_STDEV);
+    for (int i = 0; i < dMag->n; i++) {
+	if (dMag->data.F64[i] > 0.01) {
+	    mask->data.U8[i] |= 0x02;
+	}
+    }
+    residStats  = psVectorStats (residStats, resid, NULL, mask, 0x03);
+
+    // apply ApTrend results
+    psf->skyBias  = psf->ApTrend->coeff[0][0][1][0];
+    psf->skySat   = psf->ApTrend->coeff[0][0][0][1];
+    psf->ApResid  = psf->ApTrend->coeff[0][0][0][0];
+    psf->dApResid = residStats->sampleStdev;
+    psf->ApTrend->coeff[0][0][1][0] = 0;
+    psf->ApTrend->coeff[0][0][0][1] = 0;
+    psf->nApResid = residStats->clippedNvalues;
+
+    // save results for later output
+    psMetadataAdd (config, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skyBias);
+    psMetadataAdd (config, PS_LIST_TAIL, "SKYSAT",   PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skySat);
+    psMetadataAdd (config, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (config, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (config, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->growth->apLoss);
+    psMetadataAdd (config, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "ap residual scatter", psf->nApResid);
+
+    psLogMsg ("psphot.apresid", 3, "measure full-frame aperture residual: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot.apresid", 4, "aperture residual: %f +/- %f : %f bias\n", psf->ApResid, psf->dApResid, psf->skyBias);
+    psLogMsg ("psphot.apresid", 4, "apresid trends: %f %f %f %f %f\n", 
+	      1e3*psf->ApTrend->coeff[1][0][0][0],
+	      1e6*psf->ApTrend->coeff[2][0][0][0],
+	      1e6*psf->ApTrend->coeff[1][1][0][0],
+	      1e3*psf->ApTrend->coeff[0][1][0][0],
+	      1e6*psf->ApTrend->coeff[0][2][0][0]);
+
+    psFree (mask);
+    psFree (xPos);
+    psFree (yPos);
+    psFree (flux);
+    psFree (r2rflux);
+    psFree (apResid);
+    psFree (dMag);
+
+    psFree (fit);
+    psFree (resid);
+    psFree (stats);
+    psFree (residStats);
+    return true;
+}
+
+/*
+  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
+  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
+  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
+*/
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotApplyPSF.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotApplyPSF.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotApplyPSF.c	(revision 22342)
@@ -0,0 +1,61 @@
+# include "psphot.h"
+
+// fit psf model to all objects 
+bool psphotApplyPSF (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky) 
+{ 
+    float x;
+    float y;
+    int   Nfit  = 0;
+    int   Nsub  = 0;
+    int   Niter = 0;
+
+    psTimerStart ("psphot");
+
+    psphotInitLimitsPSF (config);
+    psphotInitRadiusPSF (config, sky, psf->type);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	if (source->mode  & PM_SOURCE_BLEND) continue; 
+	if (source->type == PM_SOURCE_DEFECT) continue; 
+	if (source->type == PM_SOURCE_SATURATED) continue;
+
+	// use the source moments, etc to guess basic model parameters
+	pmModel *modelEXT = pmSourceModelGuess (source, psf->type); 
+
+	// set PSF parameters for this model
+	pmModel *model = pmModelFromPSF (modelEXT, psf);
+	psFree (modelEXT);
+
+	source->modelPSF = model;
+
+	// sets the model radius (via source->model) and adjusts pixels as needed
+	psphotCheckRadiusPSF (readout, source, model);
+
+	x = model->params->data.F32[2];
+	y = model->params->data.F32[3];
+
+	// fit PSF model (set/unset the pixel mask)
+	psImageKeepCircle (source->mask, x, y, model->radius, "OR", PSPHOT_MASK_MARKED);
+	pmSourceFitModel (source, model, true);
+	psImageKeepCircle (source->mask, x, y, model->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+	Niter += model[0].nIter;
+	Nfit ++;
+
+	// check if model fit is acceptable
+	if (psphotEvalPSF (source, source->modelPSF)) {
+	    pmSourceSubModel (source->pixels, source->mask, source->modelPSF, false, false);
+	    source->mode |=  PM_SOURCE_SUBTRACTED;
+	    source->mode &= ~PM_SOURCE_TEMPSUB;
+	    Nsub ++;
+	}
+    }
+
+    psLogMsg ("psphot", 3, "fit PSF models: %f sec for %d objects (%d total iterations)\n", psTimerMark ("psphot"), Nfit, Niter);
+    psLogMsg ("psphot", 4, "subtracted %d PSF models\n", Nsub);
+    return (true);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotArguments.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotArguments.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotArguments.c	(revision 22342)
@@ -0,0 +1,181 @@
+# include "psphot.h"
+
+static void usage (void);
+
+ppConfig *psphotArguments (int *argc, char **argv) {
+
+    int N;
+
+    if (*argc == 1) usage ();
+
+    // basic pslib options
+    psLogSetFormat ("M");
+
+    ppConfig *config = ppConfigAlloc ();
+
+    // load config data from default locations 
+    if (!pmConfigRead(&config->site, &config->camera, &config->recipe, argc, argv, PSPHOT_RECIPE)) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit (EXIT_FAILURE);
+    }
+
+    // Parse other command-line arguments
+    config->arguments = psMetadataAlloc ();
+
+    // arguments (must be supplied for each run, or not used)
+    // mask image (input) is used by psphotImageLoop
+    if ((N = psArgumentGet (*argc, argv, "-mask"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->arguments, PS_LIST_TAIL, "MASK_IMAGE", PS_META_REPLACE, "", argv[N]);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // weight image (input) is used by psphotImageLoop
+    if ((N = psArgumentGet (*argc, argv, "-weight"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->arguments, PS_LIST_TAIL, "WEIGHT_IMAGE", PS_META_REPLACE, "", argv[N]);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // residual image (output) is used by psphotOutput
+    if ((N = psArgumentGet (*argc, argv, "-resid"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->arguments, PS_LIST_TAIL, "RESID_IMAGE", PS_META_REPLACE, "", argv[N]);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // background image (output) is used by psphotOutput
+    if ((N = psArgumentGet (*argc, argv, "-background"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->arguments, PS_LIST_TAIL, "BACKGROUND_IMAGE", PS_META_REPLACE, "", argv[N]);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // background model (output) is used by psphotOutput
+    if ((N = psArgumentGet (*argc, argv, "-backmodel"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->arguments, PS_LIST_TAIL, "BACKGROUND_MODEL", PS_META_REPLACE, "", 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_SELECTION", PS_META_REPLACE, "", argv[N]);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // run the test model (requires X,Y coordinate)
+    if ((N = psArgumentGet (*argc, argv, "-modeltest"))) {
+	psMetadataAddBool (config->arguments, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
+	psMetadataAddF32  (config->arguments, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
+	psMetadataAddF32  (config->arguments, 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 (config->arguments, 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 (config->arguments, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", argv[N]);
+	    psArgumentRemove (N, argc, argv);
+	}
+	if ((N = psArgumentGet (*argc, argv, "-fitset"))) {
+	    psArgumentRemove (N, argc, argv);
+	    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", argv[N]);
+	    psArgumentRemove (N, argc, argv);
+	}
+    }
+
+    // these other options override the recipe options
+    config->options = psMetadataAlloc ();
+
+    // PSF : optional psf file to be loaded
+    if ((N = psArgumentGet (*argc, argv, "-psf"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->options, PS_LIST_TAIL, "PSF_INPUT_FILE", PS_META_REPLACE, "", argv[N]);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // PSF : optional psf file to be loaded
+    if ((N = psArgumentGet (*argc, argv, "-psfout"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->options, PS_LIST_TAIL, "PSF_OUTPUT_FILE", PS_META_REPLACE, "", 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 (config->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 (config->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 (config->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 (config->options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // other arbitrary recipe values: -D key value (all added as string)
+    while ((N = psArgumentGet (*argc, argv, "-D"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddStr (config->options, PS_LIST_TAIL, argv[N], 0, "", argv[N+1]);
+	psArgumentRemove (N, argc, argv);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // other arbitrary recipe values: -Df key value (all added as float)
+    while ((N = psArgumentGet (*argc, argv, "-Df"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddF32 (config->options, PS_LIST_TAIL, argv[N], 0, "", atof(argv[N+1]));
+	psArgumentRemove (N, argc, argv);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // other arbitrary recipe values: -Di key value (all added as int)
+    while ((N = psArgumentGet (*argc, argv, "-Di"))) {
+	psArgumentRemove (N, argc, argv);
+	psMetadataAddS32 (config->options, PS_LIST_TAIL, argv[N], 0, "", atoi(argv[N+1]));
+	psArgumentRemove (N, argc, argv);
+	psArgumentRemove (N, argc, argv);
+    }
+
+    if (*argc != 3) usage ();
+
+    // input and output positions are fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "INPUT_FILE",  PS_META_REPLACE, "", argv[1]);
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT_ROOT", PS_META_REPLACE, "", argv[2]);
+
+    psTrace(__func__, 1, "Done with psphotArguments...\n");
+    return (config);
+}
+
+static void usage (void) {
+
+    fprintf (stderr, "USAGE: psphot (image) (output)\n");
+    exit (2);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotBasicDeblend.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotBasicDeblend.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotBasicDeblend.c	(revision 22342)
@@ -0,0 +1,137 @@
+# include "psphot.h"
+
+// 2006.02.07 : no leaks
+bool psphotBasicDeblend (psArray *sources, psMetadata *config) { 
+
+    int N;
+    bool status;
+    float threshold;
+    pmSource *source, *testSource;
+
+    int Nblend = 0;
+
+    psTimerStart ("psphot");
+
+    float FRACTION = psMetadataLookupF32 (&status, config, "DEBLEND_PEAK_FRACTION");
+    if (!status) FRACTION = 0.25;
+
+    float NSIGMA   = psMetadataLookupF32 (&status, config, "DEBLEND_SKY_NSIGMA");
+    if (!status) NSIGMA = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, psphotSortByY);
+
+    // 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->counts;
+    }
+    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_BLEND) continue;
+
+	// temporary array for overlapping objects we find
+	// XXX psArrayAlloc should set ->n to 0 *and* all objects to NULL
+	// XXX I need a psArrayEmpty function to free the elements without the array
+	// XXX then, I could allocate 'overlap' once outside the loop and only
+	// XXX clear at the end of each loop
+	psArray *overlap = psArrayAlloc (100);
+	overlap->n = 0;
+
+	// 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->moments->Peak;
+	// threshold is no less than NSIGMA above the local median sigma?
+	threshold = PS_MAX (threshold, NSIGMA*sqrt(source->moments->dSky));
+
+	// generate a basic contour (set of x,y coordinates at-or-below flux level)
+	psArray *contour = pmSourceContour_EAM (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
+
+	psVector *xv = contour->data[0];
+	psVector *yv = contour->data[1];
+	for (int k = 0; k < overlap->n; k++) {
+	    testSource = overlap->data[k];
+	    if (testSource->peak->counts > source->peak->counts) 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_BLEND;
+
+		// add this to the list of source->blends
+		if (source->blends == NULL) {
+		    source->blends = psArrayAlloc (16);
+		    source->blends->n = 0;
+		}
+
+		psArrayAdd (source->blends, 16, testSource);
+
+		Nblend ++;
+		j = xv->n;
+	    }
+	}  
+	psFree (overlap);
+	psFree (contour);
+    }
+    psLogMsg ("psphot.deblend", 3, "identified %d blended objects (%f sec)\n", Nblend, psTimerMark ("psphot"));
+
+    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    if (!strcasecmp (breakPt, "DEBLEND")) exit (0);
+
+    psFree (SN);
+    psFree (index);
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotBlendFit.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotBlendFit.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotBlendFit.c	(revision 22342)
@@ -0,0 +1,66 @@
+# include "psphot.h"
+
+// XXX I don't like this name
+bool psphotBlendFit (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf) { 
+
+    int Nfit = 0;
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+    
+    // S/N limit to perform full non-linear fits
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, config, "FULL_FIT_SN_LIM");
+
+    psphotInitLimitsPSF (config);
+    psphotInitLimitsEXT (config);
+    psphotInitRadiusPSF (config, psf->type);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, config, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort ("psphot", "analysis region mis-defined");
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	if (source->mode &  PM_SOURCE_BLEND) continue;
+	if (source->type == PM_SOURCE_DEFECT) continue; 
+	if (source->type == PM_SOURCE_SATURATED) continue;
+
+	// limit selection to some SN limit
+	if (source->moments == NULL) continue;
+	if (source->moments->SN < FIT_SN_LIM) 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
+	pmSourceAddModel (source->pixels, source->mask, source->modelPSF, false, false);
+	Nfit ++;
+
+	psTrace ("psphot.blend", 5, "trying source at %f, %f\n", source->moments->x, source->moments->y);
+	
+	// try fitting PSFs, then try extended sources
+	if (psphotFitBlend (readout, source)) continue;
+	if (psphotFitBlob (readout, source, sources)) continue;
+
+	psTrace ("psphot.blend", 5, "failed fits\n");
+
+	// re-subtract PSF for object, leave local sky
+	pmSourceSubModel (source->pixels, source->mask, source->modelPSF, false, false);
+	source->mode |= PM_SOURCE_SUBTRACTED;
+	source->mode |= PM_SOURCE_TEMPSUB;
+    }
+
+    psLogMsg ("psphot", 3, "fit PSF models: %f sec for %d objects (%d total)\n", psTimerMark ("psphot"), Nfit, sources->n);
+    return (true);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotChoosePSF.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotChoosePSF.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotChoosePSF.c	(revision 22342)
@@ -0,0 +1,110 @@
+# include "psphot.h"
+
+// 2006.02.07 : no leaks!
+// try PSF models and select best option
+pmPSF *psphotChoosePSF (psMetadata *config, psArray *sources) { 
+
+    bool       	    status;
+    char       	   *modelName;
+    pmPSFtry 	   *try   = NULL;
+    psArray    	   *stars = NULL;
+
+    psTimerStart ("psphot");
+
+    // check if a PSF model is supplied by the user
+    char *psfFile = psMetadataLookupStr (&status, config, "PSF_INPUT_FILE");
+    if (status) {
+	int Nfail;
+	psMetadata *psfData = psMetadataConfigParse (NULL, &Nfail, psfFile, FALSE);
+	pmPSF *psf = pmPSFfromMD (psfData);
+	return psf;
+    }
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, config, "PSF_MAX_NSTARS");
+    if (!status) NSTARS = PS_MIN (sources->n, 200);
+
+    stars = psArrayAlloc (sources->n);
+    stars->n = 0;
+
+    // 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_PSFSTAR) psArrayAdd (stars, 200, source);
+    }
+    psLogMsg ("psphot.pspsf", 4, "selected candidate %d PSF objects\n", stars->n);
+
+    // get the fixed PSF fit radius
+    // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+
+    // get the list pointers for the PSF_MODEL entries
+    psList *list = NULL;
+    psMetadataItem *mdi = psMetadataLookup (config, "PSF_MODEL");
+    if (mdi == NULL) psAbort ("psphotChoosePSF", "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 ("psphotChoosePSF", "missing PSF_MODEL selection");
+	list = psMemIncrRefCounter(mdi->data.list);
+    }
+
+    // set up an array to store the results
+    psArray *models = psArrayAlloc (list->n);
+
+    // try each model option listed in config
+    psListIterator *iter = psListIteratorAlloc (list, PS_LIST_HEAD, FALSE); 
+    for (int i = 0; i < models->n; i++) { 
+	psMetadataItem *item = psListGetAndIncrement (iter);
+	modelName = item->data.V;
+	models->data[i] = pmPSFtryModel (stars, modelName, RADIUS);
+    }
+    psFree (iter);
+    psFree (list);
+    psFree (stars);
+
+    // select the best of the models
+    // here we are using the clippedStdev on the metric as the indicator
+    try = models->data[0];
+    int bestN = 0;
+    float bestM = try->psf->dApResid;
+    for (int i = 1; i < models->n; i++) {
+	try = models->data[i];
+	float M = try->psf->dApResid;
+	if (M < bestM) {
+	    bestM = M;
+	    bestN = i;
+	}
+    }
+    
+    // use the best model:
+    try = models->data[bestN];
+
+    // 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] & PSFTRY_MASK_EXT_FAIL) {
+	    source->mode &= ~PM_SOURCE_PSFSTAR;
+	}
+    }
+
+    // save only the best model;
+    pmPSF *psf = psMemCopy(try->psf);
+    psFree (models);
+
+    psMetadataAdd (config, PS_LIST_TAIL, "NPSFSTAR", PS_DATA_S32 | PS_META_REPLACE, "number of stars used to make PSF", psf->nPSFstars);
+
+    modelName = pmModelGetType (psf->type);
+    psLogMsg ("psphot.pspsf", 3, "select psf model: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot.pspsf", 3, "selected psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
+
+    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    if (!strcasecmp (breakPt, "PSFFIT")) exit (0);
+
+    return (psf);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotCleanup.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotCleanup.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotCleanup.c	(revision 22342)
@@ -0,0 +1,13 @@
+# include "psphot.h"
+
+void psphotCleanup () {
+
+    psTimerStop ();
+    psMemCheckCorruption (true);
+    pmModelGroupCleanup ();
+    psphotOutputCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psphot");
+    return;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotDefinePixels.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotDefinePixels.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotDefinePixels.c	(revision 22342)
@@ -0,0 +1,63 @@
+# include "psphot.h"
+
+// x,y are defined in the parent image coords of readout->image
+bool psphotDefinePixels(pmSource *mySource, 
+			const pmReadout *readout,
+			psF32 x, 
+			psF32 y,
+			psF32 Radius)
+{
+    psRegion srcRegion;
+
+    // Grab a subimage of the original image of size (2 * outerRadius).
+    srcRegion = psRegionForSquare (x, y, Radius);
+    srcRegion = psRegionForImage (readout->image, srcRegion);
+
+    mySource->pixels = psImageSubset(readout->image, srcRegion);
+    mySource->weight = psImageSubset(readout->weight, srcRegion);
+    mySource->mask   = psImageSubset(readout->mask,  srcRegion);
+    mySource->region = srcRegion;
+
+    return true;
+}
+
+bool psphotRedefinePixels(pmSource *mySource, 
+			  const pmReadout *readout,
+			  psF32 x, 
+			  psF32 y,
+			  psF32 Radius)
+{
+    bool extend;
+    psRegion newRegion;
+
+    if (Radius == 0) return false;
+
+    // check to see if new region is completely contained within old region
+    newRegion = psRegionForSquare (x, y, Radius);
+    newRegion = psRegionForImage (readout->image, newRegion);
+
+    extend = false;
+    extend |= (int)(newRegion.x0) < (int)(mySource->region.x0);
+    extend |= (int)(newRegion.x1) > (int)(mySource->region.x1);
+    extend |= (int)(newRegion.y0) < (int)(mySource->region.y0);
+    extend |= (int)(newRegion.y1) > (int)(mySource->region.y1);
+
+    extend |= (mySource->pixels == NULL);
+    extend |= (mySource->weight == NULL);
+    extend |= (mySource->mask == NULL);
+
+    // extend = true;
+    if (extend) {
+	// Grab a new subimage
+	psFree (mySource->pixels);
+	psFree (mySource->weight);
+	psFree (mySource->mask);
+
+	mySource->pixels = psImageSubset(readout->image,  newRegion);
+	mySource->weight = psImageSubset(readout->weight, newRegion);
+	mySource->mask   = psImageSubset(readout->mask,   newRegion);
+	mySource->region = newRegion;
+    }
+
+    return extend;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotEnsemblePSF.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotEnsemblePSF.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotEnsemblePSF.c	(revision 22342)
@@ -0,0 +1,242 @@
+# include "psphot.h"
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight);
+
+// 2006.02.07 : no leaks!
+// fit all reasonable sources with the linear PSF model
+bool psphotEnsemblePSF (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf, bool final) { 
+
+    bool  status;
+    float x;
+    float y;
+    float f;
+    float r;
+
+    // psRegion allArray = psRegionSet (0, 0, 0, 0);
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in spatial order
+    sources = psArraySort (sources, psphotSortByY);
+
+    // radius for source moments if star is saturated
+    // XXX we already do this in psphotSourceStats, is it still needed?
+    float INNER_RADIUS     = psMetadataLookupF32 (&status, config, "SKY_INNER_RADIUS");
+
+    // setup the PSF fit details
+    psphotInitRadiusPSF (config, psf->type);
+
+    // pre-calculate all model pixels
+    psArray  *models = psArrayAlloc (sources->n);
+    psVector *index  = psVectorAlloc (sources->n, PS_TYPE_U32);
+    models->n = 0;
+    index->n = 0;
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, config, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionFromString (region);
+    AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
+    // psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort ("psphot", "analysis region mis-defined");
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *inSource = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	// XXX EAM : should we try these anyway?
+	if (inSource->type == PM_SOURCE_DEFECT) continue; 
+	if (inSource->type == PM_SOURCE_SATURATED) continue;
+	if (final) {
+	    if (inSource->mode &  PM_SOURCE_SUBTRACTED) continue;
+	} else {
+	    if (inSource->mode &  PM_SOURCE_BLEND) continue;
+	}
+
+	if (inSource->moments->x < AnalysisRegion.x0) continue;
+	if (inSource->moments->y < AnalysisRegion.y0) continue;
+	if (inSource->moments->x > AnalysisRegion.x1) continue;
+	if (inSource->moments->y > AnalysisRegion.y1) continue;
+
+	pmSource *otSource = pmSourceAlloc ();
+
+	// really saturated stars should be re-measured for a better centroid
+	// XXX EAM : move this to a 'clear satstar function'
+	if (inSource->mode &  PM_SOURCE_SATSTAR) {
+	    status = pmSourceMoments (inSource, INNER_RADIUS);
+	}
+
+	// XXX EAM : add option to use EXT or PSF form
+	// use the source moments, etc to guess basic model parameters
+	pmModel *modelEXT = pmSourceModelGuess (inSource, psf->type); 
+	if (inSource->mode &  PM_SOURCE_SATSTAR) {
+	    modelEXT->params->data.F32[2] = inSource->moments->x;
+	    modelEXT->params->data.F32[3] = inSource->moments->y;
+	} else {
+	    // peak-up on peak (for non-sat objects)
+
+	    // ix,iy must land on inSource->pixels 
+	    int ix = PS_MAX (inSource->pixels->col0 + 1, PS_MIN (inSource->pixels->col0 + inSource->pixels->numCols - 2, inSource->peak->x));
+	    int iy = PS_MAX (inSource->pixels->row0 + 1, PS_MIN (inSource->pixels->row0 + inSource->pixels->numRows - 2, inSource->peak->y));
+
+	    psPolynomial2D *bicube = psImageBicubeFit (inSource->pixels, ix, iy);
+	    psPlane min = psImageBicubeMin (bicube);
+
+	    psTrace ("psphotEnsemblePSF", 5, "peak coord: %f %f -> %f %f\n", 
+		     modelEXT->params->data.F32[2], modelEXT->params->data.F32[3], min.x + ix, min.y + iy);
+	    
+	    // if min point is too deviant, keep the old value
+	    if ((fabs(min.x) < 1.5) && (fabs(min.y) < 1.5)) {
+		modelEXT->params->data.F32[2] = min.x + ix;
+		modelEXT->params->data.F32[3] = min.y + iy;
+	    }
+	    psFree (bicube);
+	}
+
+	// set PSF parameters for this model
+	pmModel *model = pmModelFromPSF (modelEXT, psf);
+	psFree (modelEXT);
+
+	// save the original coords
+	x = model->params->data.F32[2];
+	y = model->params->data.F32[3];
+
+	// set the fit radius based on the object flux limit and the model
+	psphotCheckRadiusPSF (readout, inSource, model);
+
+	// make temporary copies of the image pixels and mask
+	otSource->mask   = psImageCopy (NULL, inSource->mask,   PS_TYPE_U8);
+	otSource->pixels = psImageCopy (NULL, inSource->pixels, PS_TYPE_F32);
+	otSource->weight = psImageCopy (NULL, inSource->weight, PS_TYPE_F32);
+
+	// build the model image 
+	psImage *flux = otSource->pixels;
+	psImage *mask = otSource->mask;
+
+	// set model to unit peak, zero sky (we assume sky is subtracted)
+	model->params->data.F32[0] = 0.0;
+	model->params->data.F32[1] = 1.0;
+
+	// fill in the model pixel values
+	psImageInit (flux, 0.0);
+	psImageKeepCircle (mask, x, y, model->radius, "OR", PSPHOT_MASK_MARKED);
+	pmSourceAddModel (flux, mask, model, false, false);
+
+	// calculate nDOF (nPix - 1)
+	// int Nmaskpix = psImageCountPixelMask (mask, allArray, PSPHOT_MASK_SATURATED);
+	// model->nDOF  = mask->numCols*mask->numRows - Nmaskpix - 1;
+
+	// save source in list
+	otSource->modelPSF = model;
+	index->data.U32[models->n] = i;
+	psArrayAdd (models, 100, otSource);
+	psFree (otSource);
+    }
+    psLogMsg ("psphot.emsemble", 4, "built models: %f (%d objects)\n", psTimerMark ("psphot"), sources->n);
+    
+    // fill out the sparse matrix
+    psSparse *sparse = psSparseAlloc (models->n, 100);
+    psVector *weight = psVectorAlloc (models->n, PS_TYPE_F32);
+    for (int i = 0; i < models->n; i++) {
+	int N = index->data.U32[i];
+	pmSource *Fi = sources->data[N];
+	pmSource *Mi = models->data[i];
+
+	// scale by diagonal element (auto-cross-product)
+	r = pmSourceCrossProduct (Mi, Mi);
+	weight->data.F32[i] = r;
+
+	psSparseMatrixElement (sparse, i, i, 1.0);
+
+	// find the image x model value
+	f = pmSourceCrossProduct (Fi, Mi);
+	psSparseVectorElement (sparse, i, f / r);
+
+	// loop over all other stars following this one
+	for (int j = i + 1; j < models->n; j++) {
+	    pmSource *Mj = models->data[j];
+
+	    // skip over disjoint source images, break after last possible overlap
+	    if (Mi->pixels->row0 + Mi->pixels->numRows < Mj->pixels->row0) break;
+	    if (Mj->pixels->row0 + Mj->pixels->numRows < Mi->pixels->row0) continue;
+	    if (Mi->pixels->col0 + Mi->pixels->numCols < Mj->pixels->col0) continue;
+	    if (Mj->pixels->col0 + Mj->pixels->numCols < Mi->pixels->col0) continue;
+	    
+	    // got an overlap; calculate cross-product and add to output array
+	    f = pmSourceCrossProduct (Mi, Mj);
+	    psSparseMatrixElement (sparse, j, i, f / r); 
+	}
+    }
+    psLogMsg ("psphot.emsemble", 4, "built matrix: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = 0;
+    constraint.paramMax   = 1e8;
+    constraint.paramDelta = 1e8;
+
+    // solve for normalization terms (need include local sky?)
+    psSparseResort (sparse);
+    psVector *norm = psSparseSolve (NULL, constraint, sparse, 3);
+
+    // adjust models, set sources and subtract
+    for (int i = 0; i < models->n; i++) {
+	int N = index->data.U32[i];
+	pmSource *Fi = sources->data[N];
+	pmSource *Mi = models->data[i];
+
+	// if we already have a PSF model, free it.
+	psFree (Fi->modelPSF);
+
+	// need to increment counter so we can free models here and sources above
+	Fi->modelPSF = psMemCopy (Mi->modelPSF);
+
+	// assign linearly-fitted normalization
+	Fi->modelPSF->params->data.F32[1] = norm->data.F32[i];
+	Fi->modelPSF->dparams->data.F32[1] = sqrt(sqrt(2) * norm->data.F32[i] / (sparse->Bfj->data.F32[i] * weight->data.F32[i]));
+	// XXX EAM : this factor of sqrt(2) makes the errors consistent, but I don't understand it
+
+	// subtract object
+	pmSourceSubModel (Fi->pixels, Fi->mask, Fi->modelPSF, false, false);
+	Fi->mode |= PM_SOURCE_SUBTRACTED;
+	if (!final) Fi->mode |= PM_SOURCE_TEMPSUB;
+    }
+
+    // measure chisq for each source
+    for (int i = 0; final && (i < models->n); i++) {
+	int N = index->data.U32[i];
+	pmSource *Fi = sources->data[N];
+	pmModel *model = Fi->modelPSF;
+
+	x = model->params->data.F32[2];
+	y = model->params->data.F32[3];
+
+	psImageKeepCircle (Fi->mask, x, y, model->radius, "OR", PSPHOT_MASK_MARKED);
+	pmSourceChisq (model, Fi->pixels, Fi->mask, Fi->weight);
+	psImageKeepCircle (Fi->mask, x, y, model->radius, "AND", ~PSPHOT_MASK_MARKED);
+    }
+
+    psFree (index);
+    psFree (sparse);
+    psFree (models);
+    psFree (norm);
+    psFree (weight);
+
+    psLogMsg ("psphot.emsemble", 3, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight) {
+
+    double dC = 0.0;
+    int Npix = 0;
+    for (int j = 0; j < image->numRows; j++) {
+	for (int i = 0; i < image->numCols; i++) {
+	    if (mask->data.U8[j][i]) continue;
+	    if (weight->data.F32[j][i] <= 0) continue;
+	    dC += PS_SQR (image->data.F32[j][i]) / weight->data.F32[j][i];
+	    Npix ++;
+	}
+    }
+    model->nDOF = Npix - 1;
+    model->chisq = dC;
+
+    return (true);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotEvalFLT.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotEvalFLT.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotEvalFLT.c	(revision 22342)
@@ -0,0 +1,48 @@
+# include "psphot.h"
+
+bool psphotEvalEXT (pmSource *source, pmModel *model)
+{ 
+    int keep;
+
+    // do we actually have a valid EXT model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_FITTED;
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    switch (model->status) {
+      case PM_MODEL_SUCCESS:
+	break;
+      case PM_MODEL_UNTRIED:
+	source->mode &= ~PM_SOURCE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	psLogMsg ("psphot", 5, "EXT fail fit\n");
+	source->mode |= PM_SOURCE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is extended
+    source->type = PM_SOURCE_EXTENDED;
+
+    // the following source->mode information pertains to modelEXT:
+    source->mode |= PM_SOURCE_EXTMODEL;
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[1] <= 0) {
+	source->mode |= PM_SOURCE_FAIL;
+	return false;
+    } 
+
+    keep = pmModelFitStatus (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_POOR;
+    return false;
+}	
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotEvalPSF.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotEvalPSF.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotEvalPSF.c	(revision 22342)
@@ -0,0 +1,207 @@
+# include "psphot.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[4],dparams[5] to have a scatter of:
+// sigma_x / (S/N) * sqrt(2)
+// 1 / (params[4],params[5])*(S/N)
+
+// sigma_x : 1 / SX
+// dsx : 1 / (SX * SN)
+// dsx_o = hypot (1/(SX*SN), MIN_DSX)
+
+// any objects which is consistent with the PSF should have 
+// abs(dparams[5]) < N * dsxLine(mag) & abs(dparams[6]) < N * dsyLine(mag)
+// this includes a minimum buffer (DS) for the brighter objects
+
+// 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?
+# define MIN_DS 0.01
+
+static float SATURATION;
+static float PSF_MIN_SN;
+static float PSF_MAX_CHI;
+static float PSF_SHAPE_NSIGMA;
+
+bool psphotInitLimitsPSF (psMetadata *config) {
+
+    bool status;
+
+    // we may set this differently here from the value used to mark likely saturated stars
+    SATURATION       = psMetadataLookupF32 (&status, config, "SATURATION");
+    PSF_MIN_SN       = psMetadataLookupF32 (&status, config, "PSF_MIN_SN");
+    PSF_MAX_CHI      = psMetadataLookupF32 (&status, config, "PSF_MAX_CHI");
+    PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, config, "PSF_SHAPE_NSIGMA");
+
+    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_FITTED;
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    switch (model->status) {
+      case PM_MODEL_SUCCESS:
+	break;
+      case PM_MODEL_UNTRIED:
+	source->mode &= ~PM_SOURCE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	source->mode |= PM_SOURCE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_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[1] >= SATURATION) {
+	if (source->mode & PM_SOURCE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[1] <= 0) {
+	source->mode |= PM_SOURCE_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_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_SATSTAR;
+    }
+
+    SN  = model->params->data.F32[1]/model->dparams->data.F32[1];
+    SX  = model->params->data.F32[4];
+    SY  = model->params->data.F32[5];
+    dSX = model->dparams->data.F32[4];
+    dSY = model->dparams->data.F32[5];
+    Chi = model->chisq / model->nDOF;
+
+    // swing of sigma_x,y in sigmas
+    nSx = dSX / hypot (MIN_DS, 1 / (SX * SN));
+    nSy = dSY / hypot (MIN_DS, 1 / (SY * SN));
+
+    // assign PM_SOURCE_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 (keep) return true;
+
+    // this source is not a star, warn if it was a PSFSTAR
+    if (source->mode & PM_SOURCE_PSFSTAR) {
+	psLogMsg ("psphot", 5, "PSFSTAR demoted based on fit quality   (%f, %f  :  %f %f %f %f)\n", 
+		  model->params->data.F32[2], model->params->data.F32[3], nSx, nSy, SN, Chi);
+    } else {
+	psLogMsg ("psphot", 5, "fails PSF fit (%f, %f  :  %f %f %f %f)\n", 
+		  model->params->data.F32[2], model->params->data.F32[3], nSx, nSy, SN, Chi);
+    }
+
+    // object appears to be small, suspected defect
+    if ((nSx <= -PSF_SHAPE_NSIGMA) || (nSy <= -PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_DEFECT;
+	return false;
+    }
+
+    // object appears to be large, suspected extended source
+    if ((nSx >= PSF_SHAPE_NSIGMA) || (nSy >= PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_EXTENDED;
+	return false;
+    }
+
+    // poor-quality fit; only keep if nothing else works...
+    source->mode |= PM_SOURCE_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_FITTED;
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    switch (model->status) {
+      case PM_MODEL_SUCCESS:
+	break;
+      case PM_MODEL_UNTRIED:
+	source->mode &= ~PM_SOURCE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	source->mode |= PM_SOURCE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_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[1] >= SATURATION) {
+	if (source->mode & PM_SOURCE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[1] <= 0) {
+	source->mode |= PM_SOURCE_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_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_SATSTAR;
+    }
+    return true;
+}	
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotFindPeaks.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotFindPeaks.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotFindPeaks.c	(revision 22342)
@@ -0,0 +1,65 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+psArray *psphotFindPeaks (pmReadout *readout, psMetadata *config) {
+
+    bool  status = false;
+    float NSIGMA;
+    float SIGMA;
+    float threshold;
+
+    // smooth the image and weight map
+    psTimerStart ("psphot");
+
+    SIGMA  = psMetadataLookupF32 (&status, config, "PEAKS_SMOOTH_SIGMA");
+    NSIGMA = psMetadataLookupF32 (&status, config, "PEAKS_SMOOTH_NSIGMA");
+
+    psImage *smooth_im = psImageCopy (NULL, readout->image, PS_TYPE_F32);
+    psImageSmooth (smooth_im, SIGMA, NSIGMA);
+    psLogMsg ("psphot", 4, "smooth image: %f sec\n", psTimerMark ("psphot"));
+
+    psImage *smooth_wt = psImageCopy (NULL, readout->weight, PS_TYPE_F32);
+    psImageSmooth (smooth_wt, SIGMA, NSIGMA);
+    psLogMsg ("psphot", 4, "smooth weight: %f sec\n", psTimerMark ("psphot"));
+
+    // psphotSaveImage (NULL, smooth_im, "smooth.fits");
+
+    psTimerStart ("psphot");
+    for (int j = 0; j < smooth_im->numRows; j++) {
+	for (int i = 0; i < smooth_im->numCols; i++) {
+	    if (smooth_wt->data.F32[j][i] <= 0) continue;
+	    smooth_im->data.F32[j][i] = PS_SQR(smooth_im->data.F32[j][i]) / smooth_wt->data.F32[j][i];
+	}
+    }
+    psLogMsg ("psphot", 4, "built S/N map: %f sec\n", psTimerMark ("psphot"));
+
+    psTimerStart ("psphot");
+
+    // set peak threshold
+    NSIGMA = psMetadataLookupF32 (&status, config, "PEAKS_NSIGMA_LIMIT");
+    // threshold = NSIGMA*sky->sampleStdev + sky->sampleMean; 
+    threshold = PS_SQR(NSIGMA);
+    psLogMsg ("psphot", 4, "threshold: %f DN\n", threshold);
+
+    // find the peaks in the smoothed image 
+    psArray *peaks = pmFindImagePeaks (smooth_im, threshold);
+    if (peaks == NULL) psAbort ("find peaks", "no peaks found");
+    psFree (smooth_im);
+    psFree (smooth_wt);
+
+    // optional dump of all peak data
+    char *output = psMetadataLookupStr (&status, config, "PEAKS_OUTPUT_FILE");
+    if (status && (output != NULL) && (output[0])) {
+	pmPeaksWriteText (peaks, output);
+    }
+    psLogMsg ("psphot", 3, "%d peaks: %f sec\n", peaks->n, psTimerMark ("psphot"));
+
+    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    if (!strcasecmp (breakPt, "PEAKS")) exit (0);
+
+    return (peaks);
+}
+
+// In this function, we smooth the image, then search for the peaks 
+// Should we also subtract a super-binned image? (as an option?)
+// XXX : We need to gracefully handle no source detections
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotFitGalaxies.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotFitGalaxies.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotFitGalaxies.c	(revision 22342)
@@ -0,0 +1,66 @@
+# include "psphot.h"
+
+bool psphotFitExtended (pmReadout *readout, psMetadata *config, psArray *sources, psStats *skyStats)
+{ 
+    bool  status;
+    float x;
+    float y;
+    int   Nfit  = 0;
+    int   Nsub  = 0;
+    int   Niter = 0;
+
+    psTimerStart ("psphot");
+
+    float EXT_MIN_SN  	   = psMetadataLookupF32 (&status, config, "EXT_MIN_SN");
+    float EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, config, "EXT_MOMENTS_RADIUS");
+    char       *modelName  = psMetadataLookupStr (&status, config, "EXT_MODEL");
+    pmModelType modelType  = pmModelSetType (modelName);
+
+    psphotInitRadiusEXT (config, skyStats, modelType);
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// only fit suspected extended sources
+	if (source->type != PM_SOURCE_EXTENDED) continue;
+	if (source->mode  & PM_SOURCE_BLEND) continue; 
+
+	// skip possible extended sources below fit threshold
+	if (source->moments->SN < EXT_MIN_SN) continue;
+
+	// recalculate the source moments using the larger extended-source moments radius
+	status = pmSourceMoments (source, EXT_MOMENTS_RAD);
+	if (!status) {
+	  source->mode |= PM_SOURCE_FAIL;  // better choice?
+	  continue;
+	}
+
+	// use the source moments, etc to guess basic model parameters
+	pmModel  *model  = pmSourceModelGuess (source, modelType); 
+	source->modelEXT = model;
+	x = model->params->data.F32[2];
+	y = model->params->data.F32[3];
+
+	// sets the model radius (via source->model) and adjusts pixels as needed
+	psphotCheckRadiusEXT (readout, source, model);
+
+	// fit EXT (not PSF) model (set/unset the pixel mask)
+	psImageKeepCircle (source->mask, x, y, model->radius, "OR", PSPHOT_MASK_MARKED);
+	pmSourceFitModel (source, model, false);
+	psImageKeepCircle (source->mask, x, y, model->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+	Niter += model[0].nIter;
+	Nfit++;
+
+	// check if model fit is acceptable
+	if (pmModelFitStatus (model)) {
+	    pmSourceSubModel (source->pixels, source->mask, model, false, false);
+	    source->mode |=  PM_SOURCE_SUBTRACTED;
+	    source->mode &= ~PM_SOURCE_TEMPSUB;
+	    Nsub ++;
+	}
+    }
+    psLogMsg ("psphot", 3, "fit EXT models: %f sec for %d objects (%d total iterations)\n", psTimerMark ("psphot"), Nfit, Niter);
+    psLogMsg ("psphot", 4, "subtracted %d EXT objects\n", Nsub);
+    return (true);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotFullFit.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotFullFit.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotFullFit.c	(revision 22342)
@@ -0,0 +1,137 @@
+# include "psphot.h"
+
+bool psphotFullFit (pmReadout *readout, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky) 
+{ 
+    bool  status;
+    float x, y;
+    int   Nfit = 0;
+    int   Nsub = 0;
+    int   Niter = 0;
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+    
+    // extended source model parameters
+    float EXT_MIN_SN  	   = psMetadataLookupF32 (&status, config, "EXT_MIN_SN");
+    float EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, config, "EXT_MOMENTS_RADIUS");
+
+    // extended source model descriptions
+    char         *modelNameEXT = psMetadataLookupStr (&status, config, "EXT_MODEL");
+    pmModelType   modelTypeEXT = pmModelSetType (modelNameEXT);
+
+    psphotInitLimitsPSF (config);
+    psphotInitRadiusPSF (config, sky, psf->type);
+    psphotInitRadiusEXT (config, sky, modelTypeEXT);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	// XXX EAM : should we try these anyway?
+	if (source->mode &  PM_SOURCE_BLEND) continue;
+	if (source->type == PM_SOURCE_DEFECT) continue; 
+	if (source->type == PM_SOURCE_SATURATED) continue;
+
+	// save the PSF model from the Ensemble fit
+	pmModel *LIN  = pmModelCopy (source->modelPSF);
+
+	// attempt to fit the PSF model
+	pmModel *modelPSF = source->modelPSF;
+
+	// replace object in image
+	pmSourceAddModel (source->pixels, source->mask, modelPSF, false, false);
+	source->mode &= ~PM_SOURCE_SUBTRACTED;
+
+	psphotCheckRadiusPSF (readout, source, modelPSF);
+
+	x = modelPSF->params->data.F32[2];
+	y = modelPSF->params->data.F32[3];
+
+	// fit PSF model (set/unset the pixel mask)
+	psImageKeepCircle (source->mask, x, y, modelPSF->radius, "OR", PSPHOT_MASK_MARKED);
+	pmSourceFitModel (source, modelPSF, true);
+	psImageKeepCircle (source->mask, x, y, modelPSF->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+	// track model evaluations
+	Niter += modelPSF[0].nIter;
+	Nfit ++;
+
+	// does the PSF model succeed?
+	if (psphotEvalPSF (source, source->modelPSF)) {
+	    pmSourceSubModel (source->pixels, source->mask, source->modelPSF, false, false);
+	    source->mode |=  PM_SOURCE_SUBTRACTED;
+	    source->mode &= ~PM_SOURCE_TEMPSUB;
+	    psFree (LIN);
+	    Nsub ++;
+	    continue;
+	} 
+
+	// use the moments to get the extended source angle
+	// displace on either side of the main peak by 1sigma or so
+	// cut the peak in half?  use the main peak?
+	// also try fit set on (unsaturated?) objects with a blend within 2-3 sigma
+	// allow all possible nearby blends to be fit?
+	// EllipseMoments moments;
+	// moments.x2 = source->moments->Sx;
+	// moments.y2 = source->moments->Sy;
+	// moments.xy = source->moments->Sxy;
+	// EllipseAxes axes = EllipseMomentsToAxes (moments);
+
+	// skip the source if we don't think it is extended
+	if (source->type != PM_SOURCE_EXTENDED) goto subLINEAR;
+	if (source->moments->SN < EXT_MIN_SN) goto subLINEAR;
+
+	// add the double-PSF fit mode
+	// how do we compare the results?  chisq? 
+	
+	// recalculate the source moments using the larger extended-source moments radius
+	if (!pmSourceMoments (source, EXT_MOMENTS_RAD)) goto subLINEAR;
+
+	// use the source moments, etc to guess basic model parameters
+	source->modelEXT = pmSourceModelGuess (source, modelTypeEXT); 
+	pmModel *modelEXT = source->modelEXT;
+
+	psphotCheckRadiusEXT (readout, source, modelEXT);
+
+	x = modelEXT->params->data.F32[2];
+	y = modelEXT->params->data.F32[3];
+
+	// fit EXT (not PSF) model (set/unset the pixel mask)
+	psImageKeepCircle (source->mask, x, y, modelEXT->radius, "OR", PSPHOT_MASK_MARKED);
+	pmSourceFitModel (source, modelEXT, false);
+	psImageKeepCircle (source->mask, x, y, modelEXT->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+	// track model evaluations
+	Niter += modelEXT[0].nIter;
+	Nfit++;
+
+	// does the EXT model succeed?
+	if (psphotEvalEXT (source, source->modelEXT)) {
+	    pmSourceSubModel (source->pixels, source->mask, source->modelEXT, false, false);
+	    source->mode |=  PM_SOURCE_SUBTRACTED;
+	    source->mode &= ~PM_SOURCE_TEMPSUB;
+	    psFree (LIN);
+	    Nsub ++;
+	    continue;
+	}
+
+	// re-subtract PSF for object, leave local sky
+    subLINEAR:
+	psFree (source->modelPSF);
+	source->modelPSF = LIN;
+	pmSourceSubModel (source->pixels, source->mask, source->modelPSF, false, false);
+	source->mode |= PM_SOURCE_SUBTRACTED;
+	source->mode |= PM_SOURCE_TEMPSUB;
+	Nsub ++;
+    }
+
+    psLogMsg ("psphot", 3, "fit PSF models: %f sec for %d objects (%d total iterations)\n", psTimerMark ("psphot"), Nfit, Niter);
+    psLogMsg ("psphot", 4, "subtracted %d PSF objects\n", Nsub);
+    return (true);
+}
+
+
+// XXX EAM : can we distinguish the outcomes enough to re-add poor fits, etc?
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotGrowthCurve.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotGrowthCurve.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotGrowthCurve.c	(revision 22342)
@@ -0,0 +1,71 @@
+# include "psphot.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
+
+bool psphotGrowthCurve (pmReadout *readout, pmPSF *psf) { 
+
+    // bool status;
+    float xc, yc, dx, dy;
+    float fitMag, apMag;
+    float radius;
+
+    // create template model
+    pmModel *modelRef = pmModelAlloc(psf->type);
+
+    // use center of the image 
+    xc = 0.5*(readout->image->numCols + readout->image->col0);
+    yc = 0.5*(readout->image->numRows + readout->image->row0);
+    dx = psf->growth->maxRadius + 1;
+    dy = psf->growth->maxRadius + 1;
+
+    // assign the x and y coords to the image center
+    // create an object with center intensity of 1000
+    modelRef->params->data.F32[0] = 0;
+    modelRef->params->data.F32[1] = 1000;
+    modelRef->params->data.F32[2] = xc;
+    modelRef->params->data.F32[3] = yc;
+
+    // create modelPSF from this model
+    pmModel *model = pmModelFromPSF (modelRef, psf);
+
+    // 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
+    for (int i = 0; i < psf->growth->radius->n; i++) {
+
+	psImageInit (image, 0.0);
+
+	radius = psf->growth->radius->data.F32[i];
+
+	psImageKeepCircle (mask, xc, yc, radius, "OR", PSPHOT_MASK_MARKED);
+
+	pmSourceAddModel (image, mask, model, false, false);
+
+	pmSourcePhotometry (&fitMag, &apMag, model, image, mask);
+
+	psImageKeepCircle (mask, xc, yc, radius, "AND", ~PSPHOT_MASK_MARKED);
+
+	psf->growth->apMag->data.F32[i] = apMag;
+    }
+    psf->growth->fitMag = fitMag;
+
+    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);
+    psFree (modelRef);
+
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageBackground.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageBackground.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageBackground.c	(revision 22342)
@@ -0,0 +1,71 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+psPolynomial2D *psphotImageBackground (pmReadout *readout, psMetadata *config, psStats *sky) 
+{ 
+
+    // fit background to subset of image points within 3 sigma of sky
+    psTimerStart ("psphot");
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    bool      status  = false;
+    int       Npixels = image->numRows*image->numCols;
+    int       Nsubset = PS_MIN (Npixels, psMetadataLookupF32 (&status, config, "IMSTATS_NPIX"));
+    psRandom *rnd     = psRandomAlloc (PS_RANDOM_TAUS, 0);
+
+    psVector *x = psVectorAlloc (Nsubset, PS_TYPE_F32);
+    psVector *y = psVectorAlloc (Nsubset, PS_TYPE_F32);
+    psVector *z = psVectorAlloc (Nsubset, PS_TYPE_F32);
+
+    float  Sky = sky->sampleMean;
+    float dSky = 3*sky->sampleStdev;
+
+    // choose Nsubset points between 0 and Nx*Ny, convert to coords
+    int Nout = 0;
+    Npixels = image->numRows*image->numCols;
+    for (int i = 0; (i < 3*Nsubset) && (Nout < Nsubset); i++) {
+	double frnd = psRandomUniform (rnd);
+	int pixel = Npixels * frnd;
+	int ix = pixel % image->numCols;
+	int iy = pixel / image->numCols;
+	float value = image->data.F32[iy][ix];
+	if (fabs(value - Sky) > dSky) continue;
+	z->data.F32[Nout] = value;
+	x->data.F32[Nout] = ix;
+	y->data.F32[Nout] = iy;
+	Nout ++;
+    }
+    x->n = y->n = z->n = Nout;
+
+    psPolynomial2D *skyModel = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 1, 1);
+    skyModel->mask[1][1] = 1;
+
+    // psVector *mask = psVectorAlloc (Nout, PS_TYPE_U8);
+    // psVectorInit (mask, 0);
+    // psStats *fitstat = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    // skyModel = psVectorFitPolynomial2D (skyModel, mask, 1, z, NULL, x, y);
+
+    skyModel = psVectorFitPolynomial2D (skyModel, NULL, 0, z, NULL, x, y);
+	     
+    // this is a very inefficient way to evaluate the function..
+    // lame, temporary step to zero masked pixels
+    for (int j = 0; j < image->numRows; j++) {
+	for (int i = 0; i < image->numCols; i++) {
+	    if (!mask->data.U8[j][i]) {
+		Sky = psPolynomial2DEval (skyModel, i, j);
+		image->data.F32[j][i] -= Sky;
+	    }
+	}
+    }
+    sky->sampleMean = 0.0;
+
+    psFree (x);
+    psFree (y);
+    psFree (z);
+    psFree (rnd);  // XXX should this be made available at a higher level?
+
+    psLogMsg ("psphot", 3, "fit background model: %f sec\n", psTimerMark ("psphot"));
+    return (skyModel);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageLoop.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageLoop.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageLoop.c	(revision 22342)
@@ -0,0 +1,69 @@
+# include "psphot.h"
+
+bool psphotImageLoop (ppFile *file, ppConfig *config) {
+
+    bool status;
+    ppImageLoadDepth imageLoadDepth;
+
+    // determine the load depth
+    const char *depth = psMetadataLookupStr(&status, config->recipe, "LOAD.DEPTH");
+    if (! status || ! depth || strlen(depth) == 0) {
+        psLogMsg("psphot", PS_LOG_ERROR, "LOAD.DEPTH not specified in recipe.");
+        exit(EXIT_FAILURE);
+    }
+    imageLoadDepth = PP_LOAD_NONE;
+    if (!strcasecmp(depth, "FPA")) imageLoadDepth = PP_LOAD_FPA;
+    if (!strcasecmp(depth, "CHIP")) imageLoadDepth = PP_LOAD_CHIP;
+    if (!strcasecmp(depth, "CELL")) imageLoadDepth = PP_LOAD_CELL;
+    if (imageLoadDepth == PP_LOAD_NONE) {
+        psLogMsg(__func__, PS_LOG_ERROR, "LOAD.DEPTH in recipe is not FPA, CHIP or CELL.");
+        exit(EXIT_FAILURE);
+    }
+
+    if (imageLoadDepth == PP_LOAD_FPA) {
+        psTrace(__func__, 1, "Loading pixels for FPA...\n");
+        ppImageLoadPixels(file, config->database, -1, -1);
+    }
+
+    for (int i = 0; i < file->fpa->chips->n; i++) {
+        pmChip *chip = file->fpa->chips->data[i]; // Chip of interest in input image
+
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", i, chip->exists, chip->process);
+        if (! chip->process) { continue; }
+
+	if (imageLoadDepth == PP_LOAD_CHIP) {
+            psTrace(__func__, 1, "Loading pixels for chip %d...\n", i);
+            ppImageLoadPixels(file, config->database, i, -1);
+        }
+
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j]; // Cell of interest in input image
+
+            psLogMsg ("psphot", 4, "Cell %d: %x %x\n", j, cell->exists, cell->process);
+            if (! cell->process) { continue; }
+
+            if (imageLoadDepth == PP_LOAD_CELL) {
+                psTrace(__func__, 1, "Loading pixels for chip %d, cell %d...\n", i, j);
+                ppImageLoadPixels(file, config->database, i, j);
+            }
+	    
+	    // XXX optional mask and weight input image should be loaded here?
+	    // this sets the weight map and basic mask applying CELL.BAD and CELL.SATURATION
+	    pmCellSetWeights(cell);
+
+	    // I have a valid mask, now mask in the analysis region of interest
+	    pmCellSetMask (cell, config->recipe); 
+
+	    // process each of the readouts
+	    for (int k = 0; k < cell->readouts->n; k++) {
+		pmReadout *readout = cell->readouts->data[k]; // Readout of interest in input image
+
+		// run a single-model test if desired
+		psphotModelTest (readout, config->arguments, config->recipe);
+		psphotReadout (readout, config->recipe);
+		psphotOutput (readout, config->arguments);
+	    }
+        }
+    }
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageMedian.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageMedian.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageMedian.c	(revision 22342)
@@ -0,0 +1,321 @@
+# include "psphot.h"
+double psImageClippedStats (psImage *image, psImage *mask, psU8 maskValue, double fmin, double fmax);
+void fsort (float *value, int N);
+
+// random number seed to select a fraction of the image pixels
+static psRandom *rnd;
+
+// use no more than MAX_SAMPLE_PIXELS pixels for each median box
+static int MAX_SAMPLE_PIXELS;
+
+static char *backImage = NULL;
+static char *backModel = NULL;
+
+bool psphotBackgroundNames (psMetadata *arguments) {
+
+    bool status;
+
+    backImage = psMetadataLookupStr (&status, arguments, "BACKGROUND_IMAGE");
+    backModel = psMetadataLookupStr (&status, arguments, "BACKGROUND_MODEL");
+
+    return true;
+}
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+psImage *psphotImageMedian (pmReadout *readout, psMetadata *config) 
+{ 
+    bool status;
+    psRegion region;
+
+    rnd = psRandomAlloc (PS_RANDOM_TAUS, 0);
+    MAX_SAMPLE_PIXELS = psMetadataLookupF32 (&status, config, "IMSTATS_NPIX");
+    if (!status) MAX_SAMPLE_PIXELS = 1000;
+
+    psTimerStart ("psphot");
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // dimensions of input & output image
+    int Nx = image->numCols;
+    int Ny = image->numRows;
+
+    // scaling factor
+    int DX = psMetadataLookupS32 (&status, config, "BACKGROUND_XBIN");
+    if (!status) {DX = 64;}
+    int DY = psMetadataLookupS32 (&status, config, "BACKGROUND_YBIN");
+    if (!status) {DY = 64;}
+
+    // overhang : we will balance this evenly
+    int dx = (Nx % DX) / 2;
+    int dy = (Ny % DY) / 2;
+
+    // dimensions of binned image
+    int nx = (Nx % DX) ? (int)(Nx / DX) + 1 : Nx / DX;
+    int ny = (Ny % DY) ? (int)(Ny / DY) + 1 : Ny / DY;
+
+    // psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psImage *model = psImageAlloc (nx, ny, PS_TYPE_F32);
+
+    // measure clipped median for subimages 
+    for (int iy = 0; iy < ny; iy++) {
+	for (int ix = 0; ix < nx; ix++) {
+	    // sx, sy are in parent coords
+	    int sx = ix*DX - dx + image->col0;
+	    int sy = iy*DY - dy + image->row0;
+	    region = psRegionSet (sx, sx + 2*DX, sy, sy + 2*DY);
+	    region = psRegionForImage (image, region);
+	    psImage *subset  = psImageSubset (image, region);
+	    psImage *submask = psImageSubset (mask, region);
+
+	    model->data.F32[iy][ix] = psImageClippedStats (subset, submask, 0xff, 0.25, 0.50);
+
+	    psFree (subset);
+	    psFree (submask);
+
+	    // XXX psImageStats is still very inefficient and poorly coded...
+	    // stats = psImageStats (stats, subset, maskset, 0xff);
+	    // model->data.F32[iy][ix] = stats->clippedMean;
+	}
+    }
+    psLogMsg ("psphot", 3, "build median image: %f sec\n", psTimerMark ("psphot"));
+
+    // linear interpolation to full-scale
+    
+    psImage *background = psImageAlloc (Nx, Ny, PS_TYPE_F32);
+
+    // XXX this code skips the initial pixels
+    for (int Iy = 0; Iy < ny-1; Iy ++) {
+	for (int Ix = 0; Ix < nx-1; Ix ++) {
+
+	    float V00 = model->data.F32[Iy+0][Ix+0];
+	    float V01 = model->data.F32[Iy+0][Ix+1];
+	    float V10 = model->data.F32[Iy+1][Ix+0];
+	    float V11 = model->data.F32[Iy+1][Ix+1];
+
+	    // a single binned pixel quad
+	    // (Xs,Ys) : (Xe,Ye) : binned pixel centers in unbinned coords
+	    // corresponding to (Ix,Iy), (Ix+1,Iy+1)
+	    int Xs = (Ix + 1)*DX - dx;
+	    int Ys = (Iy + 1)*DY - dy;
+	    int Xe = Xs + DX;
+	    int Ye = Ys + DY;
+
+	    for (int iy = Ys; (iy < Ye) && (iy < Ny); iy++) {
+		float Vxs = (V10 - V00)*(iy - Ys) / DY + V00;
+		float Vxe = (V11 - V01)*(iy - Ys) / DY + V01;
+		float dV = (Vxe - Vxs) / DX;
+		float V  = Vxs;
+		for (int ix = Xs; (ix < Xe) && (ix < Nx); ix++) {
+		    background->data.F32[iy][ix] = V;
+		    V += dV;
+		}
+	    }
+	}
+    }
+
+    // side pixels
+    int Xs = DX - dx;
+    int Xe = nx*DX - dx;
+    for (int Iy = 0; Iy < ny - 1; Iy++) {
+
+	int Ys = (Iy + 1)*DY - dy;
+	int Ye = Ys + DY;
+
+	// leading edge
+	float V0 = model->data.F32[Iy+0][0];
+	float V1 = model->data.F32[Iy+1][0];
+	float dV = (V1 - V0) / DY;
+	float V = V0;
+	for (int iy = Ys; (iy < Ye) && (iy < Ny); iy++) {
+	    for (int ix = 0; ix < Xs; ix++) {
+		background->data.F32[iy][ix] = V;
+	    }
+	    V += dV;
+	}
+
+	// trailing edge
+	V0 = model->data.F32[Iy+0][nx-1];
+	V1 = model->data.F32[Iy+1][nx-1];
+	dV = (V1 - V0) / DY;
+	V = V0;
+	for (int iy = Ys; (iy < Ye) && (iy < Ny); iy++) {
+	    for (int ix = Xe; ix < Nx; ix++) {
+		background->data.F32[iy][ix] = V;
+	    }
+	    V += dV;
+	}
+    }
+
+    // top and bottom pixels
+    int Ys = DY - dy;
+    int Ye = ny*DY - dy;
+    for (int Ix = 0; Ix < nx - 1; Ix++) {
+
+	int Xs = (Ix + 1)*DX - dx;
+	int Xe = Xs + DX;
+
+	// top edge
+	float V0 = model->data.F32[0][Ix+0];
+	float V1 = model->data.F32[0][Ix+1];
+	float dV = (V1 - V0) / DX;
+	for (int iy = 0; iy < Ys; iy++) {
+	    float V = V0;
+	    for (int ix = Xs; (ix < Xe) && (ix < Nx); ix++) {
+		background->data.F32[iy][ix] = V;
+		V += dV;
+	    }
+	}
+
+	// bottom edge
+	V0 = model->data.F32[ny-1][Ix+0];
+	V1 = model->data.F32[ny-1][Ix+1];
+	dV = (V1 - V0) / DX;
+	for (int iy = Ye; iy < Ny; iy++) {
+	    float V = V0;
+	    for (int ix = Xs; (ix < Xe) && (ix < Nx); ix++) {
+		background->data.F32[iy][ix] = V;
+		V += dV;
+	    }
+	}
+    }
+
+    // the four corners
+    {
+	float V;
+	// 0,0
+	V = model->data.F32[0][0];
+	for (int iy = 0; iy < DY - dy; iy++) {
+	    for (int ix = 0; ix < DX - dx; ix++) {
+		background->data.F32[iy][ix] = V;
+	    }
+	}
+	// Nx,0
+	V = model->data.F32[0][nx-1];
+	for (int iy = 0; iy < DY - dy; iy++) {
+	    for (int ix = nx*DX - dx; ix < Nx; ix++) {
+		background->data.F32[iy][ix] = V;
+	    }
+	}
+	// 0,Ny
+	V = model->data.F32[ny-1][0];
+	for (int iy = ny*DY - dy; iy < Ny; iy++) {
+	    for (int ix = 0; ix < DX - dx; ix++) {
+		background->data.F32[iy][ix] = V;
+	    }
+	}
+	// Nx,Ny
+	V = model->data.F32[nx-1][ny-1];
+	for (int iy = ny*DY - dy; iy < Ny; iy++) {
+	    for (int ix = nx*DX - dx; ix < Nx; ix++) {
+		background->data.F32[iy][ix] = V;
+	    }
+	}
+    }	
+    psLogMsg ("psphot", 3, "build resampled image: %f sec\n", psTimerMark ("psphot"));
+
+    // subtract the background model
+    for (int j = 0; j < image->numRows; j++) {
+	for (int i = 0; i < image->numCols; i++) {
+	    if (!mask->data.U8[j][i]) {
+		image->data.F32[j][i] -= background->data.F32[j][i];
+	    }
+	}
+    }
+
+    // optionally save background
+    if (backImage != NULL) psphotSaveImage (NULL, background, backImage);
+    if (backModel != NULL) psphotSaveImage (NULL, model, backModel);
+
+    psLogMsg ("psphot", 3, "subtracted background model: %f sec\n", psTimerMark ("psphot"));
+    psFree (rnd);
+    psFree (model);
+    return (background);
+}
+
+double psImageClippedStats (psImage *image, psImage *mask, psU8 maskValue, double fmin, double fmax) {
+
+    double value;
+    int nx = image->numCols;
+    int ny = image->numRows;
+    
+    if (nx*ny <= 0) return 0.0;
+
+    int Nsubset = PS_MIN (MAX_SAMPLE_PIXELS, nx*ny);
+    int Npixels = nx*ny;
+
+    psVector *values = psVectorAlloc (Nsubset, PS_TYPE_F32);
+
+    float min = values->data.F32[0];
+    float max = values->data.F32[0];
+
+    int n = 0;
+    for (int i = 0; i < Nsubset; i++) {
+	double frnd = psRandomUniform (rnd);
+	int pixel = Npixels * frnd;
+	int ix = pixel % nx;
+	int iy = pixel / nx;
+
+	if (mask->data.U8[iy][ix] & maskValue) continue;
+
+	value = image->data.F32[iy][ix];
+	min = PS_MIN (value, min);
+	max = PS_MIN (value, max);
+	values->data.F32[n] = value;
+	n++;
+    }
+    values->n = n;
+
+    int imin = fmin * n;
+    int imax = fmax * n;
+    int npts = imax - imin + 1;
+
+    fsort (values->data.F32, n);
+
+    value = 0;
+    for (int i = imin; (i <= imax) && (i < n); i++) {
+	value += values->data.F32[i];
+    }
+    value = value / npts;
+    
+    // XXX correct for selection bias??
+
+    psFree (values);
+    return value;
+}
+
+void fsort (float *value, int N) {
+
+    int l,j,ir,i;
+    float temp;
+  
+    if (N < 2) return;
+    l = N >> 1;
+    ir = N - 1;
+    for (;;) {
+	if (l > 0) {
+	    temp = value[--l];
+	}
+	else {
+	    temp = value[ir];
+	    value[ir] = value[0];
+	    if (--ir == 0) {
+		value[0] = temp;
+		return;
+	    }
+	}
+	i = l;
+	j = (l << 1) + 1;
+	while (j <= ir) {
+	    if (j < ir && value[j] < value[j+1]) ++j;
+	    if (temp < value[j]) {
+		value[i]=value[j];
+		j += (i=j) + 1;
+	    }
+	    else j = ir + 1;
+	}
+	value[i] = temp;
+    }
+}
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageStats.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageStats.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotImageStats.c	(revision 22342)
@@ -0,0 +1,56 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+psStats *psphotImageStats (pmReadout *readout, psMetadata *config) 
+{ 
+    psStats *stats = NULL;
+    psStats *sky   = NULL;
+
+    psImage *image = readout->image;
+
+    // get image stats on a subset of the image (random 1e5 pts)
+    psTimerStart ("psphot");
+
+    // this section fixes a bug in the psImageStats definition:
+    // it should limit the number of elements used for the initial 
+    // pass on the stats
+    { 
+	bool     status  = false;
+	int      Npixels = image->numRows*image->numCols;
+	int      Nsubset = PS_MIN (Npixels, psMetadataLookupF32 (&status, config, "IMSTATS_NPIX"));
+	psVector *subset = psVectorAlloc (Nsubset, PS_TYPE_F32);
+	psRandom *rnd    = psRandomAlloc (PS_RANDOM_TAUS, 0);
+
+	// choose Nsubset points between 0 and Nx*Ny, convert to coords
+	Npixels = image->numRows*image->numCols;
+	for (int i = 0; i < Nsubset; i++) {
+	  double frnd = psRandomUniform (rnd);
+	  int pixel = Npixels * frnd;
+	  int ix = pixel / image->numCols;
+	  int iy = pixel % image->numCols;
+	  subset->data.F32[i] = image->data.F32[iy][ix];
+	}
+
+	// this should use ROBUST not SAMPLE median
+	// robust median is broken in pslib (0.5)
+	stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN); 
+	stats = psVectorStats (stats, subset, NULL, NULL, 0); // use a mask?
+	psFree (subset);
+	psFree (rnd);
+    }
+
+    pmCell *cell = readout->parent;	// cell containing this readout;
+    float gain = psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN"); // Cell gain
+    float noise = psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE"); // Cell read noise
+
+    // convert instrumental background to poisson stats
+    sky = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+    sky->sampleMean   = stats->sampleMedian;
+    sky->sampleStdev  = sqrt(sky->sampleMean/gain + PS_SQR(noise));
+
+    psLogMsg ("psphot", 4, "background: %f +/- %f\n", sky->sampleMean, sky->sampleStdev);
+    psLogMsg ("psphot", 3, "image stats: %f sec\n", psTimerMark ("psphot"));
+
+    psFree (stats);
+    return (sky);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotLoadPixels.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotLoadPixels.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotLoadPixels.c	(revision 22342)
@@ -0,0 +1,38 @@
+# include "psphot.h"
+
+bool ppImageLoadPixels (ppFile *input, psDB *db, int chipNum, int cellNum)
+{
+    // an input chip is valid for processing if:
+    // (((chipNum == i) || (chipNum == -1)) && chip.process)
+
+    // If we have not opened the file, skip it
+    if (input->fits == NULL) {
+        return false;
+    }
+
+    psTrace(__func__, 1, "Loading %d,%d for %s\n", chipNum, cellNum, input->filename);
+
+    // set input:valid flags according to process and chipNum/cellNum
+    for (int i = 0; i < input->fpa->chips->n; i++) {
+        pmChip *chip = input->fpa->chips->data[i]; // Chip in input image
+        chip->process = (! chip->exists && ((chipNum == i) || (chipNum == -1)));
+
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j]; // Cell in input image
+            cell->process &= chip->process;
+            cell->process = (! cell->exists && ((cellNum == i) || (cellNum == -1)));
+        }
+    }
+    
+    // Read in the input pixels
+    if (! pmFPARead(input->fpa, input->fits, input->phu, db)) {
+        psErrorStackPrint(stderr, "Unable to populate camera from input FITS file\n");
+        exit(EXIT_FAILURE);
+    }
+
+    return true;
+}
+
+// XXX this is not very efficient with fseeks : each pmFPARead is randomly accessing the file
+// XXX does this handle multi-file data?
+// XXX this does NOT preserve the state of the input valid flags
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotMagnitudes.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotMagnitudes.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotMagnitudes.c	(revision 22342)
@@ -0,0 +1,89 @@
+# include "psphot.h"
+
+// XXX EAM : the apMag should only be calculated for the brighter sources?
+// XXX EAM : SN limit set by user?
+// XXX EAM : masked region should be (optionally) elliptical
+pmModel *pmSourceMagnitudes (pmSource *source, pmPSF *psf, float apRadius) {
+
+    int status;
+    bool isPSF;
+    float x, y;
+    float rflux;
+    float radius;
+    pmModel *model;
+
+    switch (source->type) {
+      case PM_SOURCE_STAR:
+	model = source->modelPSF;
+	if (model == NULL) return NULL;
+	radius = (apRadius > 0) ? apRadius : model->radius;
+	isPSF = true;
+	break;
+
+      case PM_SOURCE_EXTENDED:
+	model = source->modelEXT;
+	if (model == NULL) return NULL;
+	radius = model->radius;
+	isPSF = false;
+	break;
+	    
+      default:
+	return NULL;
+    }
+
+    x = model->params->data.F32[2];
+    y = model->params->data.F32[3];
+
+    // replace source flux
+    pmSourceAddModel (source->pixels, source->mask, model, false, false);
+
+    // set aperture mask circle of PSF_FIT_RADIUS 
+    psImageKeepCircle (source->mask, x, y, radius, "OR", PSPHOT_MASK_MARKED);
+
+    // measure object photometry
+    status = pmSourcePhotometry (&source->fitMag, &source->apMag, model, source->pixels, source->mask);
+
+    // for PSFs, correct both apMag and fitMag to same system, consistent with infinite flux star in aperture RADIUS
+    if (isPSF && (psf != NULL)) {
+      if (psf->growth != NULL) {
+	  source->apMag += pmGrowthCurveCorrect (psf->growth, model->radius);
+      }
+
+      rflux   = pow (10.0, 0.4*source->fitMag);
+      source->apMag  -= PS_SQR(model->radius)*rflux * psf->skyBias + psf->skySat / rflux;
+      source->fitMag += psPolynomial4DEval (psf->ApTrend, x, y, 0.0, 0.0);
+    }
+
+    // unmask aperture
+    psImageKeepCircle (source->mask, x, y, radius, "AND", ~PSPHOT_MASK_MARKED);
+
+    // subtract object, leave local sky
+    pmSourceSubModel (source->pixels, source->mask, model, false, false);
+
+    if (!status) return NULL;
+    return model;
+}
+
+/*
+aprMag' - fitMag = flux*skySat + r^2*rflux*skyBias + ApTrend(x,y)
+(aprMag - flux*skySat - r^2*rflux*skyBias) - fitMAg = ApTrend(x,y)
+(aprMag - flux*skySat - r^2*rflux*skyBias) = fitMAg + ApTrend(x,y)
+
+*/
+
+bool psphotMagnitudes (psMetadata *config, psArray *sources, pmPSF *psf) {
+
+    bool status;
+
+    psTimerStart ("psphot");
+
+    float RADIUS = psMetadataLookupF32 (&status, config, "AP_RADIUS");
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	pmSourceMagnitudes (source, psf, RADIUS);
+    }	
+
+    psLogMsg ("psphot.magnitudes", 4, "measure magnitudes : %f sec for %d objects\n", psTimerMark ("psphot"), sources->n);
+    return true;
+}
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotModelGroupInit.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotModelGroupInit.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotModelGroupInit.c	(revision 22342)
@@ -0,0 +1,20 @@
+# include "psphot.h"
+
+# include "models/pmModel_TAUSS.c"
+
+static pmModelGroup userModels[] = {
+    {"PS_MODEL_TAUSS", 9, pmModelFunc_TAUSS,  pmModelFlux_TAUSS,  pmModelRadius_TAUSS,  pmModelLimits_TAUSS,  pmModelGuess_TAUSS, pmModelFromPSF_TAUSS, pmModelFitStatus_TAUSS},
+};
+
+
+void psphotModelGroupInit (void) 
+{ 
+
+    pmModelGroupInit ();
+
+    int Nmodels = sizeof (userModels) / sizeof (pmModelGroup);
+    for (int i = 0; i < Nmodels; i++) {
+	pmModelGroupAdd (&userModels[i]);
+    }
+    return;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotModelTest.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotModelTest.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotModelTest.c	(revision 22342)
@@ -0,0 +1,184 @@
+# include "psphot.h"
+# include "psEllipse.h"
+static char DEFAULT_MODE[] = "EXT";
+
+bool psphotModelTest (pmReadout *readout, psMetadata *arguments, psMetadata *recipe) {
+
+    bool status;
+    int modelType;
+    unsigned int Nfail;
+    float obsMag, fitMag, value;
+    char name[64];
+    pmPSF *psf = NULL;
+
+    psMetadataItem *item  = NULL;
+
+    // run model fitting tests on a single source
+    if (!psMetadataLookupBool (&status, arguments, "TEST_FIT")) return false;
+
+    // what fitting mode to use?
+    char *psfModeWord = psMetadataLookupStr (&status, arguments, "TEST_FIT_MODE");
+    if (!status) {
+	psfModeWord = DEFAULT_MODE;
+    }
+    bool psfMode = !strcasecmp (psfModeWord, "PSF");
+
+    // in psfMode, psf sets the model type
+    if (psfMode) {
+	char *psfFile = psMetadataLookupStr (&status, recipe, "PSF_INPUT_FILE");
+	if (!status) psAbort ("psphotModelTest", "PSF_INPUT_FILE not supplied");
+	psMetadata *psfData = psMetadataConfigParse (NULL, &Nfail, psfFile, FALSE);
+	psf = pmPSFfromMD (psfData);
+	modelType = psf->type;
+    } else {
+	// find the model: supplied by user or first in the PSF_MODEL list
+	char *modelName  = psMetadataLookupStr (&status, arguments, "TEST_FIT_MODEL");
+	if (modelName == NULL) {
+	    // get the list pointers for the PSF_MODEL entries
+
+	    psList *list = NULL;
+	    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+	    if (mdi == NULL) psAbort ("psphotModelTest", "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 ("psphotChoosePSF", "missing PSF_MODEL selection");
+		list = psMemIncrRefCounter(mdi->data.list);
+	    }
+
+	    // take the first list element
+	    item = psListGet (list, PS_LIST_HEAD);
+	    modelName = item->data.V;
+	}
+	modelType = pmModelSetType (modelName);
+	if (modelType < 0) psAbort ("fitsource", "unknown model %s", modelName);
+    }
+
+    // find the fitting parameters (try test values first)
+    float INNER = psMetadataLookupF32 (&status, arguments, "TEST_FIT_INNER_RADIUS");
+    if (!status) {
+	INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    }
+
+    float OUTER = psMetadataLookupF32 (&status, arguments, "TEST_FIT_OUTER_RADIUS");
+    if (!status) {
+	OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    }
+
+    float RADIUS = psMetadataLookupF32 (&status, arguments, "TEST_FIT_RADIUS");
+    if (!status) {
+	RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    }
+
+    float mRADIUS = psMetadataLookupF32 (&status, arguments, "TEST_MOMENTS_RADIUS");
+    if (!status) {
+	mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // define the source of interest
+    float xObj     = psMetadataLookupF32 (&status, arguments, "TEST_FIT_X");
+    float yObj     = psMetadataLookupF32 (&status, arguments, "TEST_FIT_Y");
+
+    // construct the source structures
+    pmSource *source = pmSourceAlloc();
+    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
+    psphotDefinePixels (source, readout, xObj, yObj, OUTER);
+
+    // find the local sky
+    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER);
+    if (!status) psAbort ("fitsource", "pmSourceLocalSky error");
+
+    // get the source moments
+    status = pmSourceMoments (source, mRADIUS);
+    if (!status) psAbort ("fitsource", "pmSourceLocalSky error");
+    source->peak->counts = source->moments->Peak;
+
+    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->x, source->moments->y);
+
+    EllipseMoments moments;
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    EllipseAxes axes = EllipseMomentsToAxes (moments);
+
+    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);
+    // if any parameters are defined, use those values
+    int nParams = pmModelParameterCount (modelType);
+    psF32 *params = model->params->data.F32;
+    for (int i = 0; i < nParams; i++) {
+	if (i == 2) {
+	    params[i] = xObj;
+	    continue;
+	}
+	if (i == 3) {
+	    params[i] = yObj;
+	    continue;
+	}
+	sprintf (name, "TEST_FIT_PAR%d", i);
+	value = psMetadataLookupF32 (&status, arguments, name);
+	if (status) {
+	    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);
+
+    if (psfMode) {
+	pmModel *modelPSF = pmModelFromPSF (model, psf);
+	psFree (model);
+	model = modelPSF;
+	params = model->params->data.F32;
+    }
+
+    // list model input shape
+    EllipseShape 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 = EllipseShapeToAxes (shape);
+
+    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->mask, xObj, yObj, RADIUS, "OR", PSPHOT_MASK_MARKED);
+
+    char *fitset = psMetadataLookupStr (&status, arguments, "TEST_FIT_SET");
+    if (status) {
+	status = psphotFitSet (source, model, fitset, psfMode);
+	exit (0);
+    }
+
+    status = pmSourceFitModel (source, model, psfMode);
+
+    // measure the source mags
+    pmSourcePhotometry (&fitMag, &obsMag, model, source->pixels, source->mask);
+    fprintf (stderr, "ap: %f, fit: %f, apmifit: %f\n", obsMag, fitMag, obsMag - fitMag);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    pmSourceSubModel (source->pixels, source->mask, model, false, false);
+    
+    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->mask, "mask.fits");
+
+    exit (0);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotOutput.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotOutput.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotOutput.c	(revision 22342)
@@ -0,0 +1,860 @@
+# include "psphot.h"
+
+static int   extNumber  = -1;
+
+static char *outputName = NULL;
+static char *outputRoot = NULL;
+static char *outputMode = NULL;
+static char *outputFormat = NULL;
+
+static char *extNumberFormat = NULL;
+static char *extNameKey = NULL;
+static char *extRoot    = NULL;
+static char *psfFile    = NULL;
+static char *psfSample  = NULL;
+
+void psphotOutputCleanup () {
+
+    psFree (outputRoot);
+    psFree (outputName);
+    psFree (outputMode);
+    psFree (outputFormat);
+    psFree (extNumberFormat);
+    psFree (extNameKey);
+    psFree (extRoot);
+    psFree (psfFile);
+    psFree (psfSample);
+    return;
+}
+
+void psphotOutputPrep (ppFile *file, ppConfig *config) {
+
+    bool status;
+
+    outputRoot      = psMetadataLookupStr (&status, config->arguments, "OUTPUT_ROOT");
+    if (!status) psAbort ("psphot", "output file not specified");
+
+    outputName      = psMetadataLookupStr (&status, config->recipe, "OUTPUT_NAME");
+    outputMode      = psMetadataLookupStr (&status, config->recipe, "OUTPUT_MODE");
+    outputFormat    = psMetadataLookupStr (&status, config->recipe, "OUTPUT_FORMAT");
+
+    psfFile         = psMetadataLookupStr (&status, config->recipe, "PSF_OUTPUT_FILE");
+    psfSample       = psMetadataLookupStr (&status, config->recipe, "PSF_SAMPLE_FILE");
+
+    // rules to construct output names
+    extNumberFormat = psMetadataLookupStr (&status, config->recipe, "EXTNUMBER_FORMAT");
+    extNameKey      = psMetadataLookupStr (&status, config->recipe, "EXTNAME");
+    extRoot         = psMetadataLookupStr (&status, config->recipe, "EXTROOT");
+
+    if (extNumberFormat == NULL) {
+	extNumberFormat = psStringCopy ("%02d");
+    }
+
+    psStringStrip (outputName);
+    psStringStrip (extNameKey);
+    psStringStrip (extRoot);
+
+    // validate the outputMode and outputFormat
+    status = false;
+    status |= !strcasecmp (outputFormat, "TEXT");
+    status |= !strcasecmp (outputFormat, "OBJ");
+    status |= !strcasecmp (outputFormat, "SX");
+    status |= !strcasecmp (outputFormat, "CMP");
+    status |= !strcasecmp (outputFormat, "CMF");
+    if (!status) {
+	psLogMsg ("psphot", PS_LOG_ERROR, "invalid output format %s", outputFormat);
+	exit (1);
+    }
+
+    // validate the outputMode and outputFormat
+    status = false;
+    status |= !strcasecmp (outputMode, "MEF");
+    status |= !strcasecmp (outputMode, "SPLIT");
+    if (!status) {
+	psLogMsg ("psphot", PS_LOG_ERROR, "invalid output mode %s", outputMode);
+	exit (1);
+    }
+
+    // for MEF output, we need to open a file up front
+    if (!strcasecmp (outputMode, "MEF")) {
+	psLogMsg ("psphot", PS_LOG_ERROR, "MEF output mode unavailable");
+	exit (1);
+    }
+
+    // register background image-file names
+    psphotBackgroundNames (config->arguments);
+
+    // save so freeing config will not drop these references
+    psMemCopy (outputRoot);
+    psMemCopy (outputName);
+    psMemCopy (outputMode);
+    psMemCopy (outputFormat);
+    psMemCopy (extNumberFormat);
+    psMemCopy (extNameKey);
+    psMemCopy (extRoot);
+    psMemCopy (psfFile);
+    psMemCopy (psfSample);
+
+    return;
+}
+
+// generate the SPLIT filenames
+char *psphotSplitName (psMetadata *header) {
+
+    bool status;
+
+    char *newName = NULL;     // destination for resulting name
+    char *extNameWord = NULL; // this contains the per-extension word to add
+    char  extNumberWord[16];  // this will store the string-formatted number
+
+    // extNumberFormat must be set to a valid entry in psphotOutputPrep
+    sprintf (extNumberWord, extNumberFormat, extNumber);
+
+    // find the extname:
+    char *extNameVal = psMetadataLookupStr (&status, header, extNameKey);
+	    
+    extNameWord = extNameVal;
+    if ((extRoot != NULL) && (extNameVal != NULL)) {
+	// check that the extNameVal matches the expected root 
+	if (strncmp (extNameVal, extRoot, strlen(extRoot))) {
+	    psLogMsg ("psphotSplitName", PS_LOG_WARN, "header entry does not match expected format");
+	}
+	extNameWord = extNameVal + strlen(extRoot);
+    }
+
+    if (extNameWord != NULL) {
+	psStringStrip (extNameWord);
+    }
+
+    // note : if the user mis-specifies the output name, we will happily overwrite files
+    newName = psStringCopy (outputName);
+    newName = psphotNameSubstitute (newName, outputRoot, "%r");
+    newName = psphotNameSubstitute (newName, extNameWord, "%x");
+    newName = psphotNameSubstitute (newName, extNumberWord, "%n");
+
+    return newName;
+}
+
+
+// given the input string, search for the key, and replace with the replacement
+// the input string may be freed if not needed
+char *psphotNameSubstitute (char *input, char *replace, char *key) {
+
+    char *p;
+
+    if (key == NULL) return input;
+    if (strlen(key) == 0) return input;
+
+    p = strstr (input, key);
+    if (p == NULL) return input;
+
+    // we have input = xxxkeyxxx
+    // we want output = xxxreplacexxx
+
+    // this is safe since we will subtract strlen(key) elements from input
+    char *output = psAlloc(strlen(input) + strlen(replace) + 1);
+    int Nc = p - input;
+
+    // copy the first segement into 'output'
+    strncpy (output, input, Nc);
+
+    // copy the key replacement to the start of the key
+
+    strcpy (&output[Nc], replace);
+    Nc += strlen (replace);
+    
+    // copy the remainder to the end of the replacement
+    strcpy (&output[Nc], p + strlen(key));
+
+    psFree (input);
+    return output;
+}
+
+// output functions: we have several fixed modes (sx, obj, cmp)
+void psphotOutput (pmReadout *readout, psMetadata *arguments) {
+
+    bool status;
+    char *outputFile = NULL;
+
+    psMetadata *header = pmReadoutGetHeader (readout);
+
+    psArray *sources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+
+    // starting value is -1
+    extNumber ++;
+
+    // for SPLIT output, we need to open a file for each output call
+    if (!strcasecmp (outputMode, "SPLIT")) {
+	outputFile = psphotSplitName (header);
+    } 
+
+    char *residImage = psMetadataLookupStr (&status, arguments, "RESID_IMAGE");
+
+    if (psfSample != NULL) psphotSamplePSFs (psf, readout->image, psfSample);
+    if (residImage != NULL) psphotSaveImage (header, readout->image, residImage);
+
+    if (psfFile != NULL) {
+	psMetadata *psfData = pmPSFtoMD (NULL, psf);
+	psMetadataConfigWrite (psfData, psfFile);
+	psFree (psfData);
+    }
+    if (outputFile == NULL) {
+	psLogMsg ("output", 3, "no data output file selected");
+	return;
+    }
+    if (outputFormat == NULL) {
+	psLogMsg ("output", 3, "no data output format selected");
+	psFree (outputFile);
+	return;
+    }
+    if (!strcasecmp (outputFormat, "SX")) {
+	pmSourcesWriteSX (sources, outputFile);
+	psFree (outputFile);
+	return;
+    }
+    if (!strcasecmp (outputFormat, "OBJ")) {
+	pmSourcesWriteOBJ (sources, outputFile);
+	psFree (outputFile);
+	return;
+    }
+    if (!strcasecmp (outputFormat, "CMP")) {
+	pmSourcesWriteCMP (sources, outputFile, header);
+	psFree (outputFile);
+	return;
+    }
+    if (!strcasecmp (outputFormat, "CMF")) {
+	pmSourcesWriteCMF (sources, outputFile, header);
+	psFree (outputFile);
+	return;
+    }
+    if (!strcasecmp (outputFormat, "TEXT")) {
+	pmSourcesWriteText (sources, outputFile);
+	psFree (outputFile);
+	return;
+    }
+
+    psAbort ("psphot", "unknown output mode %s", outputFormat);
+}
+
+// dophot-style output list with fixed line width
+bool pmSourcesWriteOBJ (psArray *sources, char *filename) {
+
+    int type;
+    psF32 *PAR, *dPAR;
+    float dmag, apResid;
+
+    psTimerStart ("string");
+
+    psLine *line = psLineAlloc (104);  // 104 is dophot-defined line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("WriteSourceOBJ", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+
+    // write sources with models 
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	pmModel *model = pmModelSelect (source);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	dmag = dPAR[1] / PAR[1];
+	type = pmSourceDophotType (source);
+	apResid = source->apMag - source->fitMag;
+
+	psLineInit (line);
+	psLineAdd (line, "%3d",   type);
+	psLineAdd (line, "%8.2f", PAR[2]);
+	psLineAdd (line, "%8.2f", PAR[3]);
+	psLineAdd (line, "%8.3f", source->fitMag);
+	psLineAdd (line, "%6.3f", dmag);
+	psLineAdd (line, "%9.2f", PAR[0]);
+	psLineAdd (line, "%9.3f", PAR[4]);
+	psLineAdd (line, "%9.3f", PAR[5]);
+	psLineAdd (line, "%7.2f", PAR[6]);
+	psLineAdd (line, "%8.3f", 99.999);
+	psLineAdd (line, "%8.3f", source->apMag);
+	psLineAdd (line, "%8.2f\n", apResid);
+	fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    psFree (line);
+    fprintf (stderr, "%f seconds for %d objects with psLine\n", psTimerMark ("string"), (int)sources->n);
+
+    psTimerStart ("string");
+
+    f = fopen ("test.obj", "w");
+    if (f == NULL) {
+	psLogMsg ("WriteSourceOBJ", 3, "can't open output file for output %s\n", "test.obj");
+	return false;
+    }
+
+    char *string;
+    // write sources with models 
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	pmModel *model = pmModelSelect (source);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	dmag = dPAR[1] / PAR[1];
+	type = pmSourceDophotType (source);
+	apResid = source->apMag - source->fitMag;
+
+	string = NULL;
+	psStringAppend (&string, "%3d",   type);
+	psStringAppend (&string, "%8.2f", PAR[2]);
+	psStringAppend (&string, "%8.2f", PAR[3]);
+	psStringAppend (&string, "%8.3f", source->fitMag);
+	psStringAppend (&string, "%6.3f", dmag);
+	psStringAppend (&string, "%9.2f", PAR[0]);
+	psStringAppend (&string, "%9.3f", PAR[4]);
+	psStringAppend (&string, "%9.3f", PAR[5]);
+	psStringAppend (&string, "%7.2f", PAR[6]);
+	psStringAppend (&string, "%8.3f", 99.999);
+	psStringAppend (&string, "%8.3f", source->apMag);
+	psStringAppend (&string, "%8.2f\n", apResid);
+	fwrite (string, 1, strlen(string), f);
+	psFree (string);
+    }
+    fclose (f);
+    fprintf (stderr, "%f seconds for %d objects with psString\n", psTimerMark ("string"), (int)sources->n);
+
+    return true;
+}
+
+// elixir-mode / sextractor-style output list with fixed line width
+bool pmSourcesWriteSX (psArray *sources, char *filename) {
+
+    psF32 *PAR, *dPAR;
+    float dmag;
+
+    psLine *line = psLineAlloc (110);  // 110 is sextractor line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("WriteSourceSX", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+
+    // write sources with models 
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	pmModel *model = pmModelSelect (source);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	// pmSourceSextractType (source, &type, &flags); XXX EAM : implement me...
+
+	dmag = dPAR[1] / PAR[1];
+
+	psLineInit (line);
+	psLineAdd (line, "%5.2f", 0.0); // should be type
+	psLineAdd (line, "%11.3f", PAR[2]);
+	psLineAdd (line, "%11.3f", PAR[3]);
+	psLineAdd (line, "%9.4f", source->fitMag);
+	psLineAdd (line, "%9.4f", dmag);
+	psLineAdd (line, "%13.4f", PAR[0]);
+	psLineAdd (line, "%9.2f", 0.0); // should be FWHMx
+	psLineAdd (line, "%9.2f", 0.0); // should be FWHMy
+	psLineAdd (line, "%6.1f", 0.0); // should be Theta
+	psLineAdd (line, "%9.4f", 99.999); // should be MAG_ISO
+	psLineAdd (line, "%9.4f", source->apMag);
+	psLineAdd (line, "%4d\n", 0); // should be flags
+	fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    return true;
+}
+
+// elixir-style pseudo FITS table (header + ascii list)
+bool pmSourcesWriteCMP (psArray *sources, char *filename, psMetadata *header) {
+
+    int i, type;
+    psMetadataItem *mdi;
+    psF32 *PAR, *dPAR;
+    float dmag, lsky;
+    bool status;
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+    if (!status) ZERO_POINT = 25.0;
+
+    // write necessary information to output header
+    psMetadataAdd (header, PS_LIST_TAIL, "NSTARS",   PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS", sources->n);
+
+    // create file, write-out header
+    psFits *fits = psFitsOpen (filename, "w");
+
+    // set NAXIS to 0 : CFITSIO requires isolated header to have NAXIS = 0
+    mdi = psMetadataLookup (header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    // psMetadataAdd (header, PS_LIST_HEAD, "NAXIS", PS_DATA_S32 | PS_META_REPLACE, "", 0);
+    psFitsWriteHeader (header, fits);
+    psFitsClose (fits);
+
+    // re-open, add data to end of file
+    FILE *f = fopen (filename, "a+");
+    if (f == NULL) {
+	psLogMsg ("WriteSourceOBJ", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+    fseek (f, 0, SEEK_END);
+
+    psLine *line = psLineAlloc (67);  // 66 is imclean-defined line length
+
+    // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	pmModel *model = pmModelSelect (source);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	dmag = dPAR[1] / PAR[1];
+	type = pmSourceDophotType (source);
+	lsky = (PAR[0] < 1.0) ? 0.0 : log10(PAR[0]);
+
+	psLineInit (line);
+	psLineAdd (line, "%6.1f ", PAR[2]);
+	psLineAdd (line, "%6.1f ", PAR[3]);
+	psLineAdd (line, "%6.3f ", PS_MIN (99.999, source->fitMag + ZERO_POINT));
+	psLineAdd (line, "%03d ",  PS_MIN (999, (int)(1000*dmag)));
+	psLineAdd (line, "%2d ",   type);
+	psLineAdd (line, "%3.1f ", lsky);
+	psLineAdd (line, "%6.3f ", 99.999); // should be 'Mgal
+	psLineAdd (line, "%6.3f ", PS_MIN (99.999, source->apMag + ZERO_POINT)); 
+	psLineAdd (line, "%6.2f ", PAR[4]); // should be 'FHWM x'
+	psLineAdd (line, "%6.2f ", PAR[5]); // should be 'FHWM y'
+	psLineAdd (line, "%5.1f\n", 0);	// should be theta
+	fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    return true;
+}
+
+// elixir-style FITS table output (header + table in 1st extension)
+// this format consists of a header derived from the image header
+// followed by a zero-size matrix, followed by the table data
+bool pmSourcesWriteCMF (psArray *sources, char *filename, psMetadata *header) {
+
+    psArray *table;
+    psMetadataItem *mdi;
+    psMetadata *row;
+    psMetadata *theader;
+    int i, type;
+    psF32 *PAR, *dPAR;
+    float dmag, lsky;
+    bool status;
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+
+    table = psArrayAlloc (sources->n);
+    table->n = 0;
+
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	pmModel *model = pmModelSelect (source);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	dmag = dPAR[1] / PAR[1];
+	type = pmSourceDophotType (source);
+	lsky = (PAR[0] < 1.0) ? 0.0 : log10(PAR[0]);
+
+	row = psMetadataAlloc ();
+	psMetadataAdd (row, PS_LIST_TAIL, "X_PIX",   PS_DATA_F32, "", PAR[2]);
+	psMetadataAdd (row, PS_LIST_TAIL, "Y_PIX",   PS_DATA_F32, "", PAR[3]);
+	psMetadataAdd (row, PS_LIST_TAIL, "MAG_RAW", PS_DATA_F32, "", source->fitMag + ZERO_POINT);
+	psMetadataAdd (row, PS_LIST_TAIL, "MAG_ERR", PS_DATA_F32, "", (int)(1000*dmag));
+	psMetadataAdd (row, PS_LIST_TAIL, "MAG_GAL", PS_DATA_F32, "", type);
+	psMetadataAdd (row, PS_LIST_TAIL, "MAG_AP",  PS_DATA_F32, "", lsky);
+	psMetadataAdd (row, PS_LIST_TAIL, "LOG_SKY", PS_DATA_F32, "", 32.0);
+	psMetadataAdd (row, PS_LIST_TAIL, "FWHM_X",  PS_DATA_F32, "", source->apMag + ZERO_POINT); 
+	psMetadataAdd (row, PS_LIST_TAIL, "FWHM_Y",  PS_DATA_F32, "", PAR[4]);
+	psMetadataAdd (row, PS_LIST_TAIL, "THETA",   PS_DATA_F32, "", PAR[5]);
+	psMetadataAdd (row, PS_LIST_TAIL, "DOPHOT",  PS_DATA_STRING, "", "0");
+	psMetadataAdd (row, PS_LIST_TAIL, "DUMMY",   PS_DATA_STRING, "", "123");
+    
+	psArrayAdd (table, 100, row);
+	psFree (row);
+    }
+
+    // write the correct number of sources in the header
+    psMetadataAdd (header, PS_LIST_TAIL, "NSTARS", PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS", table->n);
+    psMetadataAdd (header, PS_LIST_TAIL, "EXTEND", PS_DATA_BOOL | PS_META_REPLACE, "this file has extensions", true);
+
+    // set NAXIS to 0 (we don't write out the data array)
+    mdi = psMetadataLookup (header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    // create the basic table header
+    theader = psMetadataAlloc ();
+    psMetadataAdd (theader, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING, "extension name", "SMPFILE");
+
+    // write out the header and table
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteHeader (header, fits);
+    psFitsWriteTable (fits, theader, table);
+    psFitsClose (fits);
+
+    return true;
+}
+
+/***** Text Output Methods *****/
+
+bool pmSourcesWriteText (psArray *sources, char *filename) {
+
+    char *name = (char *) psAlloc (strlen(filename) + 10);
+
+    sprintf (name, "%s.psf.dat", filename);
+    pmModelWritePSFs (sources, name);
+
+    sprintf (name, "%s.ext.dat", filename);
+    pmModelWriteEXTs (sources, name);
+
+    sprintf (name, "%s.nul.dat", filename);
+    pmModelWriteNULLs (sources, name);
+
+    sprintf (name, "%s.mnt.dat", filename);
+    pmMomentsWriteText (sources, name);
+
+    psFree (name);
+    return true;
+}
+
+// write the PSF sources to an output file
+bool pmModelWritePSFs (psArray *sources, char *filename) {
+
+    double dPos, dMag;
+    int i, j;
+    FILE *f;
+    psF32 *PAR, *dPAR;
+    pmModel  *model;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("pmModelWritePSFs", 3, "can't open output file for moments%s\n", filename);
+	return false;
+    }
+
+    // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	if (source->type != PM_SOURCE_STAR) continue;
+	model = source->modelPSF;
+	if (model == NULL) continue;
+
+	PAR  = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+	
+	// dPos is positional error, dMag is mag error
+	dPos = hypot (dPAR[2], dPAR[3]);
+	dMag = dPAR[1] / PAR[1];
+
+	fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ", 
+		 PAR[2], PAR[3], PAR[0], source->fitMag, dMag, dPos);
+
+	for (j = 4; j < model->params->n; j++) {
+	    fprintf (f, "%9.6f ", PAR[j]);
+	}
+	fprintf (f, " : ");
+	for (j = 4; j < model->params->n; j++) {
+	    fprintf (f, "%9.6f ", dPAR[j]);
+	}
+	fprintf (f, ": %8.4f %2d %#5x %7.3f %7.1f %7.2f %4d %2d\n", 
+		 source[0].apMag, source[0].type, source[0].mode, 
+		 log10(model[0].chisq/model[0].nDOF), 
+		 source[0].moments->SN, 
+		 model[0].radius,
+		 model[0].nDOF, 
+		 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmModelWriteEXTs (psArray *sources, char *filename) {
+
+    double dPos, dMag;
+    int i, j;
+    FILE *f;
+    psF32 *PAR, *dPAR;
+    pmModel  *model;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("pmModelWriteEXTs", 3, "can't open output file for moments%s\n", filename);
+	return false;
+    }
+
+    // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+
+	if (source->type != PM_SOURCE_EXTENDED) continue;
+	model = pmSourceMagnitudes (source, NULL, 0.0);
+	if (model == NULL) continue;
+
+	PAR  = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+	
+	// dPos is shape error
+	// XXX these are hardwired for SGAUSS
+	dPos = hypot ((dPAR[4] / PAR[4]), (dPAR[5] / PAR[5]));
+	dMag = dPAR[1] / PAR[1];
+
+	fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ", 
+		 PAR[2], PAR[3], PAR[0], source->fitMag, dMag, dPos);
+
+	for (j = 4; j < model->params->n; j++) {
+	    fprintf (f, "%9.6f ", PAR[j]);
+	}
+	fprintf (f, " : ");
+	for (j = 4; j < model->params->n; j++) {
+	    fprintf (f, "%9.6f ", dPAR[j]);
+	}
+	fprintf (f, ": %7.4f  %2d %#5x %7.3f %7.1f %7.2f %4d %2d\n", 
+		 source->apMag, 
+		 source[0].type, source[0].mode,
+		 log10(model[0].chisq/model[0].nDOF), 
+		 source[0].moments->SN, 
+		 model[0].radius, 
+		 model[0].nDOF, 
+		 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmModelWriteNULLs (psArray *sources, char *filename) 
+{
+
+    int i;
+    FILE *f;
+    pmMoments *moment = NULL;
+    pmSource *source = NULL;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("DumpObjects", 3, "can't open output file for moments%s\n", filename);
+	return false;
+    }
+
+    pmMoments *empty = pmMomentsAlloc ();
+
+    // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	source = sources->data[i];
+
+	// skip these sources (in PSF or EXT)
+	if (source->type == PM_SOURCE_STAR) continue;
+	if (source->type == PM_SOURCE_EXTENDED) continue;
+	    
+	if (source->moments == NULL) {
+	    moment = empty;
+	} else {
+	    moment = source->moments;
+	}
+
+	fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %8.1f %7.1f %7.1f %7.1f  %4d %2d\n", 
+		 source->peak->x, source->peak->y, source->peak->counts, 
+		 source->moments->x, source->moments->y, 
+		 source->moments->Sx, source->moments->Sy, 
+		 source->moments->Sum, source->moments->Peak, 
+		 source->moments->Sky, source->moments->SN, 
+		 source->moments->nPixels, source->type); 
+    }
+    fclose (f);
+    psFree (empty);
+    return true;
+}
+
+// write the moments to an output file
+bool pmMomentsWriteText (psArray *sources, char *filename)
+{
+
+    int i;
+    FILE *f;
+    pmSource *source = NULL;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmMomentsWriteText", 3, "can't open output file for moments%s\n", filename);
+        return false;
+    }
+
+    for (i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        if (source->moments == NULL)
+            continue;
+        fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %10.1f %7.1f %7.1f %7.1f  %4d %2d %#5x\n",
+                 source->peak->x, source->peak->y, source->peak->counts,
+                 source->moments->x, source->moments->y,
+                 source->moments->Sx, source->moments->Sy,
+                 source->moments->Sum, source->moments->Peak,
+                 source->moments->Sky, source->moments->SN,
+                 source->moments->nPixels, source->type, source->mode);
+    }
+    fclose (f);
+    return true;
+}
+
+// write the peaks to an output file
+bool pmPeaksWriteText (psArray *peaks, char *filename) {
+
+    int i;
+    FILE *f;
+	
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("pmPeaksWriteText", 3, "can't open output file for peaks%s\n", filename);
+	return false;
+    }
+
+    for (i = 0; i < peaks->n; i++) {
+	pmPeak *peak = peaks->data[i];
+	if (peak == NULL) continue;
+	fprintf (f, "%5d %5d  %7.1f\n", 
+		 peak->x, peak->y, peak->counts); 
+    }
+    fclose (f);
+    return true;
+}
+
+// translations between psphot object types and dophot object types
+int pmSourceDophotType (pmSource *source) {
+
+    switch (source->type) {
+
+      case PM_SOURCE_DEFECT:
+      case PM_SOURCE_SATURATED:
+	return (8);
+
+      case PM_SOURCE_STAR:
+	if (source->mode & PM_SOURCE_SATSTAR) return (10);
+	if (source->mode & PM_SOURCE_POOR) return (7);
+	if (source->mode & PM_SOURCE_FAIL) return (4);
+	return (1);
+
+      case PM_SOURCE_EXTENDED:
+	return (2);
+
+      default:
+	return (0);
+    }
+    return (0);
+}
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteImage (fits, NULL, image, 0);
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+psImage *pmModelPSFatXY (psImage *image, pmModel *modelEXT, pmPSF *psf, int x, int y, int dx, int dy) {
+
+    psRegion region = {x - dx, x + dx, y - dy, y + dy};
+    psImage *view = psImageSubset (image, region);
+    psImage *sample = psImageCopy (NULL, view, PS_TYPE_F32);
+    psImageInit (sample, 0);
+    modelEXT->params->data.F32[2] = x;
+    modelEXT->params->data.F32[3] = y;
+    pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+    pmSourceAddModel (sample, NULL, modelPSF, false, false);
+    psFree (modelPSF);
+    return (sample);
+}
+
+bool psphotSamplePSFs (pmPSF *psf, psImage *image, char *output) {
+
+    // make sample PSFs for 4 corners and the center
+    psImage *sample;
+
+    // optional dump of all rough source data
+    if (output[0] == 0) return false;
+
+    pmModel *modelEXT = pmModelAlloc (psf->type);
+    modelEXT->params->data.F32[0] = 0;
+    modelEXT->params->data.F32[1] = 1;
+
+    psFits *fits = psFitsOpen (output, "w");
+
+    // the centers are in parent coordinates; they do not need to correspond to valid pixels...
+    sample = pmModelPSFatXY (image, modelEXT, psf, 25, 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, image->numCols - 25, image->numRows - 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, image->numCols - 25, 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, 25, image->numRows - 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, image->numCols / 2, image->numRows / 2, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+bool psphotDumpMoments (psMetadata *config, psArray *sources) {
+
+    bool status;
+
+    // optional dump of all rough source data
+    char *output = psMetadataLookupStr (&status, config, "MOMENTS_OUTPUT_FILE");
+    if (!status) return false;
+    if (output == NULL) return false;
+    if (output[0] == 0) return false;
+
+    pmMomentsWriteText (sources, output);
+    return true;
+}
+
+bool psphotUpdateHeader (psMetadata *header, psMetadata *config) {
+
+    // write necessary information to output header
+    psMetadataItemTransfer (header, config, "NSTARS");
+    psMetadataItemTransfer (header, config, "ZERO_PT");
+    psMetadataItemTransfer (header, config, "APMIFIT");
+    psMetadataItemTransfer (header, config, "dAPMIFIT");
+    psMetadataItemTransfer (header, config, "SKYBIAS");
+    psMetadataItemTransfer (header, config, "PHOTCODE");
+    
+    // write necessary information to output header
+    psMetadataItemTransfer (header, config, "NPSFSTAR");
+    psMetadataItemTransfer (header, config, "SKYBIAS");
+    psMetadataItemTransfer (header, config, "SKYSAT");
+    psMetadataItemTransfer (header, config, "APMIFIT");
+    psMetadataItemTransfer (header, config, "DAPMIFIT");
+    psMetadataItemTransfer (header, config, "NAPMIFIT");
+    psMetadataItemTransfer (header, config, "APLOSS");
+
+    // XXX these need to be defined from elsewhere
+    psMetadataAdd (header, PS_LIST_TAIL, "FWHM_X",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM X",          0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "FWHM_Y",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Y",          0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "ANGLE",    PS_DATA_F32 | PS_META_REPLACE, "PSF ANGLE",           0.0);
+    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);
+
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotParseCamera.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotParseCamera.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotParseCamera.c	(revision 22342)
@@ -0,0 +1,96 @@
+# include "psphot.h"
+
+# define METADATA_ADD_DEFAULT(CONFIG, TYPE, NAME, VALUE, COMMENT)\
+    { psMetadataItem *ITEM; \
+      ITEM = psMetadataLookup (CONFIG, NAME); \
+      if (ITEM == NULL) { \
+	psMetadataAdd##TYPE (CONFIG, PS_LIST_TAIL, NAME, 0, COMMENT, VALUE); \
+      } }
+
+// 2006.02.07 : no leaks!
+ppFile *psphotParseCamera (ppConfig *config) {
+
+    ppFile *input = ppFileAlloc ();
+
+    input->filename = psMetadataLookupStr(NULL, config->arguments, "INPUT_FILE");
+    psMemCopy (input->filename); // keep for external use
+
+    // Open the input image
+    psLogMsg("psphot", PS_LOG_INFO, "Opening input image: %s\n", input->filename);
+    input->fits = psFitsOpen (input->filename, "r"); // File handle for FITS file
+    if (! input->fits) {
+        psErrorStackPrint(stderr, "Can't open input image: %s\n", input->filename);
+        exit(EXIT_FAILURE);
+    }
+    input->phu = psFitsReadHeader(NULL, input->fits); // FITS header
+
+    // Get camera configuration from header if not already defined
+    if (! config->camera) {
+        config->camera = pmConfigCameraFromHeader(config->site, input->phu);
+        if (! config->camera) {
+             // There's no point in continuing if we can't recognize what we've got
+            psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+            exit(EXIT_FAILURE);
+        }
+   } else if (! pmConfigValidateCamera(config->camera, input->phu)) {
+       // There's no point in continuing if what we've got doesn't match what we've been told
+        psError(PS_ERR_IO, true, "%s does not seem to be from the specified camera.\n",
+                input->filename);
+        exit(EXIT_FAILURE);
+    }
+
+    // Determine the correct recipe to use (from camera or from user)
+    // if config->recipe is not NULL, it is a user-supplied recipe
+    if (!config->recipe) {
+	config->recipe = pmConfigRecipeFromCamera(config->camera, PSPHOT_RECIPE);
+	if (config->recipe == NULL) {
+	    psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
+	    exit(EXIT_FAILURE);
+	}
+    }
+
+    // recipe override values (command-line options):
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (config->options, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+	psMetadataAddItem (config->recipe, item, PS_LIST_TAIL, PS_META_REPLACE);
+    }
+    psFree (iter);
+
+    // set default recipe values here
+    METADATA_ADD_DEFAULT (config->recipe, Str, "FITMODE", "NONE", "");
+    METADATA_ADD_DEFAULT (config->recipe, Str, "PHOTCODE", "NONE", "");
+    METADATA_ADD_DEFAULT (config->recipe, Str, "BREAK_POINT", "NONE", "");
+    METADATA_ADD_DEFAULT (config->recipe, Str, "OUTPUT_FORMAT", "CMP", "");
+    METADATA_ADD_DEFAULT (config->recipe, Str, "OUTPUT_MODE", "SPLIT", "");
+
+    // Chip selection: if we are using a single chip, select it for each FPA
+    // Chip numbers to work on: -chip 1,2,3,5,8,10
+    char *chips = psMetadataLookupStr(NULL, config->arguments, "CHIP_SELECTIONS"); 
+    if (chips != NULL) {
+	char *p = chips;
+	while (strlen(p)) {
+	    char *q = strchr (p, ',');
+	    if (q == NULL) { 
+		q = p + strlen(p);
+	    } else {
+		*q = 0;
+		q = q + 1;
+	    }
+	    int chipNum = atoi(p);
+	    // XXX EAM : extend this to allow an array of selected chips (by name)
+	    if (! pmFPASelectChip(input->fpa, chipNum)) {
+		psErrorStackPrint(stderr, "Chip number %d doesn't exist in camera.\n", chipNum);
+		exit(EXIT_FAILURE);
+	    }
+	    psLogMsg("psphot", PS_LOG_INFO, "Operating only on chip %d\n", chipNum);
+	    p = q;
+        }
+    }
+
+    // Construct cameras in preparation for reading
+    input->fpa = pmFPAConstruct(config->camera);
+
+    psTrace(__func__, 1, "Done with psphotParseCamera...\n");
+    return input;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotRadiusChecks.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotRadiusChecks.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotRadiusChecks.c	(revision 22342)
@@ -0,0 +1,81 @@
+# include "psphot.h"
+
+static float PSF_FIT_NSIGMA;
+static float PSF_FIT_PADDING;
+static pmModelRadius modelRadiusPSF;
+
+bool psphotInitRadiusPSF (psMetadata *config, pmModelType type) {
+
+    bool status;
+
+    PSF_FIT_NSIGMA   = psMetadataLookupF32 (&status, config, "PSF_FIT_NSIGMA");
+    PSF_FIT_PADDING  = psMetadataLookupF32 (&status, config, "PSF_FIT_PADDING");
+
+    // this function specifies the radius at this the model hits the given flux
+    modelRadiusPSF       = pmModelRadius_GetFunction (type);
+    return true;
+}
+
+bool psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model) {
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radius = modelRadiusPSF (model->params, PSF_FIT_NSIGMA*moments->dSky) + PSF_FIT_PADDING;
+    if (isnan(model->radius)) psAbort ("apply_psf_model", "error in radius");
+	
+    if (source->mode &  PM_SOURCE_SATSTAR) {
+	model->radius *= 2;
+    }
+
+    bool status = psphotRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radius);
+    return status;
+}
+
+bool psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR) {
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radius = modelRadiusPSF (model->params, PSF_FIT_NSIGMA*moments->dSky) + dR + PSF_FIT_PADDING;
+    if (isnan(model->radius)) psAbort ("apply_psf_model", "error in radius");
+	
+    if (source->mode &  PM_SOURCE_SATSTAR) {
+	model->radius *= 2;
+    }
+
+    bool status = psphotRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radius);
+    return status;
+}
+
+static float EXT_FIT_NSIGMA;
+static float EXT_FIT_PADDING;
+static pmModelRadius modelRadiusEXT;
+
+bool psphotInitRadiusEXT (psMetadata *config, pmModelType type) {
+
+    bool status;
+
+    EXT_FIT_NSIGMA   = psMetadataLookupF32 (&status, config, "EXT_FIT_NSIGMA");
+    EXT_FIT_PADDING  = psMetadataLookupF32 (&status, config, "EXT_FIT_PADDING");
+
+    // this function specifies the radius at this the model hits the given flux
+    modelRadiusEXT       = pmModelRadius_GetFunction (type);
+    return true;
+}
+
+bool psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model) {
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radius = modelRadiusEXT (model->params, EXT_FIT_NSIGMA*moments->dSky) + EXT_FIT_PADDING;
+    if (isnan(model->radius)) psAbort (__func__, "error in radius");
+
+    // redefine the pixels if needed
+    bool status = psphotRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radius);
+    return status;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotReadout.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotReadout.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotReadout.c	(revision 22342)
@@ -0,0 +1,97 @@
+# include "psphot.h"
+
+// XXX 2006.02.07 : no leaks!
+bool psphotReadout (pmReadout *readout, psMetadata *config) {
+
+    psArray     *sources  = NULL;
+    psArray     *peaks    = NULL;
+    psImage     *skymodel = NULL;
+    pmPSF       *psf      = NULL;
+    bool         status;
+
+    // generate a background model (median, smoothed image)
+    skymodel = psphotImageMedian (readout, config);
+
+    // find the peaks in the image
+    peaks = psphotFindPeaks (readout, config);
+
+    // construct sources and measure basic stats
+    // limit moments analysis by S/N?
+    sources = psphotSourceStats (readout, config, peaks);
+
+    // classify sources based on moments, brightness
+    // faint sources not classified?
+    psphotRoughClass (sources, config);
+
+    // mark blended peaks PS_SOURCE_BLEND
+    psphotBasicDeblend (sources, config);
+
+    // use bright stellar objects to measure PSF
+    psf = psphotChoosePSF (config, sources);
+
+    // linear PSF fit to peaks
+    psphotEnsemblePSF (readout, config, sources, psf, FALSE);
+
+    // non-linear PSF and EXT fit to brighter sources
+    psphotBlendFit (readout, config, sources, psf);
+
+    // replace fitted sources
+    psphotReplaceUnfit (sources);
+
+    // find remaining peaks after first source subtraction pass
+    // psphotFindPeaks ();
+
+    // linear PSF fit to remaining peaks
+    psphotEnsemblePSF (readout, config, sources, psf, TRUE);
+
+    // measure aperture photometry corrections
+    psphotApResid (readout, sources, config, psf);
+
+    // calculate source magnitudes
+    psphotMagnitudes (config, sources, psf);
+
+    // update the header with stats results
+    psMetadata *header = pmReadoutGetHeader (readout);
+    psphotUpdateHeader (header, config);
+
+    // XXX make this an option?
+    // replace background in residual image
+    psphotSkyReplace (readout, skymodel);
+
+    // need to do something with the sources, psf, and sky
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES",   PS_DATA_ARRAY,   "psphot sources", sources);
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF",       PS_DATA_UNKNOWN, "psphot psf", psf);
+    // XXX : replace? status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SKY.MEAN",  PS_DATA_F32,     "psphot sky mean", sky->sampleMean);
+    // XXX : replace? status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SKY.SIGMA", PS_DATA_F32,     "psphot sky stdev", sky->sampleStdev);
+    // XXX : what should I do with the skymodel image?
+
+    // free up the local copies of the data
+    psFree (psf);
+    psFree (sources);
+    psFree (peaks);
+    psFree (skymodel);
+    return true;
+}
+
+
+// XXX Deprecate or allow these versions?
+# if (0)
+    // select analysis method
+    char *FITMODE = psMetadataLookupStr (&status, config, "FITMODE");
+    if (!strcasecmp(FITMODE, "ENSEMBLE")) {
+	psphotEnsemblePSF (readout, config, sources, psf);
+    }
+
+    if (!strcasecmp(FITMODE, "FULL")) {
+	psphotEnsemblePSF (readout, config, sources, psf);
+	psphotFullFit (readout, config, sources, psf);
+	psphotReplaceUnfit (sources);
+	psphotApResid (readout, sources, config, psf);
+    }
+
+    if (!strcasecmp(FITMODE, "BASIC")) {
+	psphotApplyPSF (readout, config, sources, psf);
+	psphotFitExtended (readout, config, sources);
+    }
+
+# endif
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotReplaceUnfit.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotReplaceUnfit.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotReplaceUnfit.c	(revision 22342)
@@ -0,0 +1,21 @@
+# include "psphot.h"
+
+bool psphotReplaceUnfit (psArray *sources) { 
+
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+	if (!(source->mode & PM_SOURCE_TEMPSUB)) continue;
+	if (source->modelPSF == NULL) continue;
+
+	pmSourceAddModel (source->pixels, source->mask, source->modelPSF, false, false);
+	source->mode &= ~PM_SOURCE_SUBTRACTED;
+	source->mode &= ~PM_SOURCE_TEMPSUB;
+    }
+    psLogMsg ("psphot.replace", 3, "replace unfitted models: %f sec (%d objects)\n", psTimerMark ("psphot"), sources->n);
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotRoughClass.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotRoughClass.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotRoughClass.c	(revision 22342)
@@ -0,0 +1,21 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+bool psphotRoughClass (psArray *sources, psMetadata *config) {
+
+    bool status;
+    pmPSFClump   psfClump;
+
+    psfClump = pmSourcePSFClump (sources, config);
+
+    // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
+    pmSourceRoughClass (sources, config, psfClump);
+
+    // optional printout of source moments only
+    psphotDumpMoments (config, sources);
+
+    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    if (!strcasecmp (breakPt, "CLASS")) exit (0);
+
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotSetup.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotSetup.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotSetup.c	(revision 22342)
@@ -0,0 +1,117 @@
+# include "psphot.h"
+
+// load the image
+// load or construct the mask
+//   apply X/Y border info
+//   apply the SATURATE mask?
+// load or construct the weight 
+
+pmReadout *psphotSetup (psMetadata *config, psMetadata **header) 
+{
+    psMetadata *myHeader;
+    psImage *image = NULL;
+    psImage *weight = NULL;
+    psImage *mask = NULL;
+    psRegion region = {0,0,0,0};	// a region representing the entire array
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // load implementation-specific models
+    psphotModelGroupInit ();
+
+    // setup header template, specify COMMENT and HISTORY as MULTI
+    // XXX EAM - shouldn't this be part of the basic FITS concept?
+    myHeader = psMetadataAlloc ();
+    psMetadataAdd (myHeader, PS_LIST_HEAD, "COMMENT", PS_DATA_METADATA_MULTI, "folder for comment", NULL);
+    psMetadataAdd (myHeader, PS_LIST_HEAD, "HISTORY", PS_DATA_METADATA_MULTI, "folder for history", NULL);
+
+    // read header and image data from INPUT 
+    char *input = psMetadataLookupPtr (&status, config, "IMAGE");
+    if (!status) psAbort ("psphot", "input image not specified");
+    psFits *file = psFitsOpen (input, "r");
+    myHeader = psFitsReadHeader (myHeader, file);
+    psImage *tmpimage = psFitsReadImage (NULL, file, region, 0);
+    image = psImageCopy (NULL, tmpimage, PS_TYPE_F32);
+    psFitsClose (file);
+    
+    // psFree (input);
+    // XXX EAM - does the current API expect to get a copy or a view?
+
+    // grab these values from the approrpiate location (image header if necessary)
+    float RDNOISE  = pmConfigLookupF32 (&status, config, myHeader, "RDNOISE");
+    float GAIN     = pmConfigLookupF32 (&status, config, myHeader, "GAIN");
+    psMetadataAdd (config, PS_LIST_TAIL, "RDNOISE", PS_DATA_F32 | PS_META_REPLACE, "read noise value used", RDNOISE);
+    psMetadataAdd (config, PS_LIST_TAIL, "GAIN",    PS_DATA_F32 | PS_META_REPLACE, "gain value used", GAIN);
+
+    // load the weight image if it is supplied, otherwise build from input
+    // by 'weight' we really mean variance image
+    char *weightName = psMetadataLookupPtr (&status, config, "WEIGHT");
+    if (status == true) {
+	file = psFitsOpen (weightName, "r");
+	weight = psFitsReadImage  (NULL, file, region, 0);
+	psFitsClose (file);
+	// psFree (weightName); XXX - see psFree (input)
+    } else {
+	// build the weight image from the input image, RDNOISE, and GAIN
+	weight = psImageAlloc (image->numCols, image->numRows, PS_TYPE_F32);
+	for (int iy = 0; iy < image->numRows; iy++) {
+	    for (int ix = 0; ix < image->numCols; ix++) {
+		weight->data.F32[iy][ix] = PS_MAX (image->data.F32[iy][ix] / GAIN + PS_SQR(RDNOISE/GAIN), 0.0);
+	    }
+	}
+    }
+
+    // load the mask if specified, otherwise construct 
+    char *maskName = psMetadataLookupPtr (&status, config, "MASK");
+    if (status == true) {
+	// XXX EAM require / convert mask to psU8?
+	file = psFitsOpen (maskName, "r");
+	mask  = psFitsReadImage  (NULL, file, region, 0);
+	psFitsClose (file);
+	// psFree (maskName); XXX - see psFree (input)
+    } else {
+	mask = psImageAlloc (image->numCols, image->numRows, PS_TYPE_U8);
+    }
+
+    // mask the excluded outer pixels
+    float XMIN  = psMetadataLookupF32 (&status, config, "XMIN");
+    float XMAX  = psMetadataLookupF32 (&status, config, "XMAX");
+    float YMIN  = psMetadataLookupF32 (&status, config, "YMIN");
+    float YMAX  = psMetadataLookupF32 (&status, config, "YMAX");
+    psRegion keep = psRegionSet (XMIN, XMAX, YMIN, YMAX);
+    keep = psRegionForImage (image, keep);
+    psImageKeepRegion (mask, keep, "OR", PSPHOT_MASK_INVALID);
+
+    // mask the saturated pixels
+    // XXX EAM does the mask need to grow?
+    float SATURATION = psMetadataLookupF32 (&status, config, "SATURATION");
+    for (int i = 0; status && (i < image->numRows); i++) {
+	for (int j = 0; j < image->numCols; j++) {
+	    if (image->data.F32[i][j] >= SATURATION) {
+		mask->data.U8[i][j] |= PSPHOT_MASK_SATURATED;
+	    }
+	}
+    }
+
+    // mask the pixels below min threshold
+    float MIN_VALID = psMetadataLookupF32 (&status, config, "MIN_VALID_PIXEL");
+    for (int i = 0; status && (i < image->numRows); i++) {
+	for (int j = 0; j < image->numCols; j++) {
+	    if (image->data.F32[i][j] < MIN_VALID) {
+		mask->data.U8[i][j] |= PSPHOT_MASK_INVALID;
+	    }
+	}
+    }
+
+    psLogMsg ("psphot", 3, "load data: %f sec\n", psTimerMark ("psphot"));
+
+    // return image data
+    pmReadout *readout = pmReadoutAlloc(NULL);
+    readout->image = image;
+    readout->weight = weight;
+    readout->mask = mask;
+
+    *header = myHeader;
+    return (readout);
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotSkyReplace.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotSkyReplace.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotSkyReplace.c	(revision 22342)
@@ -0,0 +1,21 @@
+# include "psphot.h"
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotSkyReplace (pmReadout *readout, psImage *background) { 
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // replace the background model
+    for (int j = 0; j < image->numRows; j++) {
+	for (int i = 0; i < image->numCols; i++) {
+	    if (!mask->data.U8[j][i]) {
+		image->data.F32[j][i] += background->data.F32[j][i];
+	    }
+	}
+    }
+    
+    return true;
+}
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotSortBySN.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotSortBySN.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotSortBySN.c	(revision 22342)
@@ -0,0 +1,34 @@
+# include "psphot.h"
+
+// sort by SN (descending)
+int psphotSortBySN (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->moments == NULL) ? 0 : A->moments->SN;
+    psF32 fB = (B->moments == NULL) ? 0 : B->moments->SN;
+    if (isnan (fA)) fA = 0;
+    if (isnan (fB)) fB = 0;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (-1);
+    if (diff < FLT_EPSILON) return (+1);
+    return (0);
+}
+
+// sort by Y (ascending)
+int psphotSortByY (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->moments == NULL) ? 0 : A->moments->y;
+    psF32 fB = (B->moments == NULL) ? 0 : B->moments->y;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (+1);
+    if (diff < FLT_EPSILON) return (-1);
+    return (0);
+}
+
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotSourceFits.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotSourceFits.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotSourceFits.c	(revision 22342)
@@ -0,0 +1,313 @@
+# include "psphot.h"
+# include "psEllipse.h"
+
+// given a source with an existing modelPSF, attempt a full PSF fit, subtract if successful
+
+bool psphotFitPSF (pmReadout *readout, pmSource *source) { 
+
+    float x, y;
+
+    // save the PSF model from the Ensemble fit
+    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->radius, "OR", PSPHOT_MASK_MARKED);
+    pmSourceFitModel (source, PSF, true);
+    psImageKeepCircle (source->mask, x, y, PSF->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+    // does the PSF model succeed?
+    if (!psphotEvalPSF (source, PSF)) {
+	psFree (PSF);
+	return false;
+    }
+
+    psTrace ("psphot.blend", 5, "fitted as PSF\n");
+    pmSourceSubModel (source->pixels, source->mask, PSF, false, false);
+
+    // free old model, save new model 
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+
+    source->mode |=  PM_SOURCE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_TEMPSUB;
+    return true;
+}
+
+
+static float EXT_MIN_SN;
+static float EXT_MOMENTS_RAD;
+static pmModelType modelTypeEXT;
+
+bool psphotInitLimitsEXT (psMetadata *config) {
+
+    bool status;
+
+    // extended source model parameters
+    EXT_MIN_SN       = psMetadataLookupF32 (&status, config, "EXT_MIN_SN");
+    EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, config, "EXT_MOMENTS_RADIUS");
+
+    // extended source model descriptions
+    char *modelNameEXT = psMetadataLookupStr (&status, config, "EXT_MODEL");
+    modelTypeEXT = pmModelSetType (modelNameEXT);
+    psphotInitRadiusEXT (config, modelTypeEXT);
+
+    return true;
+}
+
+bool psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources) { 
+
+    bool okEXT, okDBL;
+    float chiEXT, chiDBL;
+
+    // skip the source if we don't think it is extended
+    if (source->type == PM_SOURCE_UNKNOWN) return false;
+    if (source->type == PM_SOURCE_DEFECT) return false;
+    if (source->type == PM_SOURCE_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.blend", 5, "trying blob...\n");
+
+    // this temporary source is used as a place-holder by the psphotEval functions below
+    pmSource *tmpSrc = pmSourceAlloc ();
+
+    pmModel *EXT = psphotFitEXT (readout, source);
+    okEXT = psphotEvalEXT (tmpSrc, EXT);
+    chiEXT = EXT->chisq / EXT->nDOF;
+
+    psArray *DBL = psphotFitDBL (readout, source);
+    okDBL  = psphotEvalDBL (tmpSrc, DBL->data[0]);
+    okDBL &= psphotEvalDBL (tmpSrc, DBL->data[1]);
+    pmModel *ONE = DBL->data[0];
+    chiDBL = ONE->chisq / ONE->nDOF;
+
+    psFree (tmpSrc); // XXX should I keep / save the flags set in the eval functions?
+
+    if (okEXT && okDBL) {
+	psTrace ("psphot.blend", 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);
+    pmSourceSubModel (source->pixels, source->mask, EXT, false, false);
+    psTrace ("psphot.blend", 5, "blob as EXT: %f %f\n", EXT->params->data.F32[2], EXT->params->data.F32[3]);
+
+    // save new model
+    source->modelEXT = EXT;
+    source->mode |=  PM_SOURCE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_TEMPSUB;
+    return true;
+
+keepDBL:
+    // sub DLB
+    psFree (EXT);
+    pmSourceSubModel (source->pixels, source->mask, (pmModel *) DBL->data[0], false, false);
+    pmSourceSubModel (source->pixels, source->mask, (pmModel *) DBL->data[1], false, false);
+    psTrace ("psphot.blend", 5, "blob as DBL: %f %f\n", ONE->params->data.F32[2], ONE->params->data.F32[3]);
+
+    // drop old model, save new second model...
+    psFree (source->modelPSF);
+    source->modelPSF = psMemCopy (DBL->data[0]);
+    source->mode    |= PM_SOURCE_SUBTRACTED;
+    source->mode    |= PM_SOURCE_PAIR;
+    source->mode    &= ~PM_SOURCE_TEMPSUB;
+
+    // copy most data from the primary source (modelEXT, blends stay NULL)
+    pmSource *newSrc = pmSourceAlloc ();
+    newSrc->peak     = psMemCopy (source->peak);
+    newSrc->pixels   = psMemCopy (source->pixels);
+    newSrc->weight   = psMemCopy (source->weight);
+    newSrc->mask     = psMemCopy (source->mask);
+    newSrc->moments  = psMemCopy (source->moments);
+    newSrc->modelPSF = psMemCopy (DBL->data[1]);
+    newSrc->type     = source->type;
+    newSrc->mode     = source->mode;
+    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) { 
+
+    float x, y, dx, dy;
+    pmModel *DBL;
+    pmModel *PSF;
+    EllipseAxes axes;
+    EllipseMoments moments;
+    psArray *modelSet;
+    
+    // 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 = EllipseMomentsToAxes (moments);
+
+    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);
+
+    modelSet = psArrayAlloc (2);
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[1] *= 0.5;
+    DBL->params->data.F32[2] = source->moments->x + dx;
+    DBL->params->data.F32[3] = source->moments->y + dy;
+    modelSet->data[0] = DBL;
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[1] *= 0.5;
+    DBL->params->data.F32[2] = source->moments->x - dx;
+    DBL->params->data.F32[3] = source->moments->y - dy;
+    modelSet->data[1] = DBL;
+
+    x = source->moments->x;
+    y = source->moments->y;
+
+    // fit EXT (not PSF) model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radius, "OR", PSPHOT_MASK_MARKED);
+    pmSourceFitSet (source, modelSet, true);
+    psImageKeepCircle (source->mask, x, y, PSF->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+    return (modelSet);
+}
+
+pmModel *psphotFitEXT (pmReadout *readout, pmSource *source) { 
+
+    float x, y;
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *EXT = pmSourceModelGuess (source, modelTypeEXT); 
+
+    psphotCheckRadiusEXT (readout, source, EXT);
+
+    x = EXT->params->data.F32[2];
+    y = EXT->params->data.F32[3];
+
+    // fit EXT (not PSF) model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, EXT->radius, "OR", PSPHOT_MASK_MARKED);
+    pmSourceFitModel (source, EXT, false);
+    psImageKeepCircle (source->mask, x, y, EXT->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+    return (EXT);
+}
+
+bool psphotFitBlend (pmReadout *readout, pmSource *source) { 
+
+    float x, y, dR;
+
+    // if this source is not a possible blend, just fit as PSF
+    if ((source->blends == NULL) || (source->mode & PM_SOURCE_SATSTAR)) {
+	bool status = psphotFitPSF (readout, source);
+	return status;
+    }
+    psTrace ("psphot.blend", 5, "trying blend...\n");
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+
+    x = PSF->params->data.F32[2];
+    y = PSF->params->data.F32[3];
+
+    psArray *modelSet = psArrayAlloc (source->blends->n + 1);
+    modelSet->n = 0;
+    psArrayAdd (modelSet, 16, PSF);
+    
+    psArray *sourceSet = psArrayAlloc (source->blends->n + 1);
+    sourceSet->n = 0;
+    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->x - x, blend->peak->y - y));
+
+	// create the model and guess parameters for this blend
+	blend->modelPSF = pmModelAlloc (PSF->type);
+	pmModel *model = blend->modelPSF;
+
+	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[1] = blend->moments->Peak - blend->moments->Sky;
+	model->params->data.F32[2] = blend->peak->x;
+	model->params->data.F32[3] = blend->peak->y;
+
+	// add this blend to the list
+	psArrayAdd (modelSet, 16, model);
+	psArrayAdd (sourceSet, 16, blend);
+    }
+
+    // extend source radius as needed
+    psphotCheckRadiusPSFBlend (readout, source, PSF, dR);
+
+    // fit PSF model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radius, "OR", PSPHOT_MASK_MARKED);
+    pmSourceFitSet (source, modelSet, true);
+    psImageKeepCircle (source->mask, x, y, PSF->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+    // evaluate the blend objects, subtract if good, free otherwise
+    for (int i = 1; i < modelSet->n; i++) {
+	pmSource *src = sourceSet->data[i];
+	pmModel *model = modelSet->data[i];
+
+	// if we skip this one, free the corresponding blend entry model
+	if (!psphotEvalPSF (src, model)) {
+	    pmSource *blend = source->blends->data[i - 1];
+	    psFree (blend->modelPSF);
+	    blend->modelPSF = NULL;
+	    continue;
+	}
+
+	psTrace ("psphot.blend", 5, "fitted blend as PSF\n");
+	pmSourceSubModel (source->pixels, source->mask, model, false, false);
+	src->mode |=  PM_SOURCE_SUBTRACTED;
+	src->mode &= ~PM_SOURCE_TEMPSUB;
+    }
+    psFree (modelSet);
+    psFree (sourceSet);
+
+    // evaluate the primary object
+    if (!psphotEvalPSF (source, PSF)) {
+	psFree (PSF);
+	return false;
+    }
+
+    psTrace ("psphot.blend", 5, "fitted primary as PSF\n");
+    pmSourceSubModel (source->pixels, source->mask, PSF, false, false);
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    source->mode |=  PM_SOURCE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_TEMPSUB;
+    return true;
+}
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotSourceStats.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotSourceStats.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotSourceStats.c	(revision 22342)
@@ -0,0 +1,80 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+psArray *psphotSourceStats (pmReadout *readout, psMetadata *config, psArray *peaks) 
+{
+    bool     status  = false;
+    psArray *sources = NULL;
+    float BIG_RADIUS;
+
+    psTimerStart ("psphot");
+
+    // determine properties (sky, moments) of initial sources
+    float INNER    = psMetadataLookupF32 (&status, config, "SKY_INNER_RADIUS");
+    float OUTER    = psMetadataLookupF32 (&status, config, "SKY_OUTER_RADIUS");
+    float RADIUS   = psMetadataLookupF32 (&status, config, "PSF_MOMENTS_RADIUS");
+
+    sources = psArrayAlloc (peaks->n);
+    sources->n = 0;
+
+    for (int i = 0; i < peaks->n; i++) {
+
+	// create a new source, add peak
+	pmSource *source = pmSourceAlloc();
+	source->peak = (pmPeak *)psMemCopy(peaks->data[i]);
+
+	// allocate image, weight, mask arrays for each peak (square of radius OUTER)
+	psphotDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+
+	// XXX skip faint sources?
+
+	// 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);
+	if (!status) {
+	  psFree (source);
+	  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);
+	if (!status) {
+	  psFree (source);
+	  continue;
+	}
+
+	// measure basic source moments
+	status = pmSourceMoments (source, RADIUS);
+	if (status) {
+	    // add to the source array
+	    psArrayAdd (sources, 100, source);
+	    psFree (source);
+	    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);
+	    continue;
+	}
+
+	psFree (source);
+	continue;
+    }
+     
+    psLogMsg ("psphot", 3, "%d moments: %f sec\n", sources->n, psTimerMark ("psphot"));
+
+    char *breakPt = psMetadataLookupStr (&status, config, "BREAK_POINT");
+    if (!strcasecmp (breakPt, "STATS")) exit (0);
+
+    return (sources);
+}
+
+// XXX EAM : filter out bad peaks (eg, no valid pixels available for sky)
Index: /tags/eam_tags/psphot_dev_09/psphot/src/psphotTest.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotTest.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotTest.c	(revision 22342)
@@ -0,0 +1,84 @@
+# include "psphot.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 = psArrayAlloc (10);
+    table->n = 0;
+
+    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: /tags/eam_tags/psphot_dev_09/psphot/src/psphotTestArguments.c
===================================================================
--- /tags/eam_tags/psphot_dev_09/psphot/src/psphotTestArguments.c	(revision 22342)
+++ /tags/eam_tags/psphot_dev_09/psphot/src/psphotTestArguments.c	(revision 22342)
@@ -0,0 +1,19 @@
+# include "psphot.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);
+}
