Index: /branches/ipp-1-X-branches/rel-0-9/psastro/.cvsignore
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/.cvsignore	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/.cvsignore	(revision 21632)
@@ -0,0 +1,18 @@
+bin
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+libtool
+ltmain.sh
+stamp-h1
+config.sub
+psastro.pc
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/Makefile.am
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/Makefile.am	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/Makefile.am	(revision 21632)
@@ -0,0 +1,10 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psastro.pc
+
+EXTRA_DIST = \
+	psastro.pc.in \
+	autogen.sh
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/autogen.sh
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/autogen.sh	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/autogen.sh	(revision 21632)
@@ -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=psastro
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/configure.ac
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/configure.ac	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/configure.ac	(revision 21632)
@@ -0,0 +1,203 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.59)
+
+AC_INIT([psastro], [0.9.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------------ kapa,libkapa options -------------------------
+dnl -- libkapa implies the requirement for libpng, libjpeg as well --
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+dnl test for command-line options: use ohana-config if not supplied
+KAPA_CFLAGS_CONFIG="true"
+KAPA_LIBS_CONFIG="true"
+AC_ARG_WITH(kapa,
+[AS_HELP_STRING(--with-kapa=DIR,Specify location of libkapa)],
+[KAPA_CFLAGS="-I$withval/include" KAPA_LIBS="-L$withval/lib" 
+ KAPA_CFLAGS_CONFIG="false"       KAPA_LIBS_CONFIG="false"])
+AC_ARG_WITH(kapa-include,
+[AS_HELP_STRING(--with-kapa-include=DIR,Specify libkapa include directory.)],
+[KAPA_CFLAGS="-I$withval" KAPA_CFLAGS_CONFIG="false"])
+AC_ARG_WITH(kapa-lib,
+[AS_HELP_STRING(--with-kapa-lib=DIR,Specify libkapa library directory.)],
+[KAPA_LIBS="-L$withval" KAPA_LIBS_CONFIG="false"])
+
+echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+echo "KAPA_LIBS_CONFIG: $KAPA_LIBS_CONFIG"
+echo "KAPA_CFLAGS: $KAPA_CFLAGS"
+echo "KAPA_LIBS: $KAPA_LIBS"
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+HAVE_KAPA="true"
+AC_MSG_NOTICE([checking for libkapa])
+if test "$KAPA_CFLAGS_CONFIG" = "true" -o "$KAPA_LIBS_CONFIG" = "true"; then
+  AC_MSG_NOTICE([kapa info supplied by ohana-config])
+  KAPA_CONFIG=`which ohana-config`
+  AC_CHECK_FILE($KAPA_CONFIG,[],
+    [HAVE_KAPA="false"; AC_MSG_WARN([libkapa is not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location])])
+  
+  echo "HAVE_KAPA: $HAVE_KAPA"
+  echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_CFLAGS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa cflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa cflags])
+   KAPA_CFLAGS="`${KAPA_CONFIG} --cflags`"
+   AC_MSG_RESULT([${KAPA_CFLAGS}])
+  fi
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_LIBS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa ldflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa ldflags])
+   KAPA_LIBS="`${KAPA_CONFIG} --libs` -lX11"
+   AC_MSG_RESULT([${KAPA_LIBS}])
+  fi
+fi
+
+if test "$HAVE_KAPA" = "true" ; then
+ AC_MSG_NOTICE([libkapa supplied])
+ PSASTRO_CFLAGS="${PSASTRO_CFLAGS} ${KAPA_CFLAGS}"
+ PSASTRO_LIBS="${PSASTRO_LIBS} ${KAPA_LIBS}"
+else
+ AC_MSG_NOTICE([libkapa ignored])
+fi
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libjpeg options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(jpeg,
+[AS_HELP_STRING(--with-jpeg=DIR,Specify location of libjpeg.)],
+[JPEG_CFLAGS="-I$withval/include"
+ JPEG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(jpeg-include,
+[AS_HELP_STRING(--with-jpeg-include=DIR,Specify libjpeg include directory.)],
+[JPEG_CFLAGS="-I$withval"])
+AC_ARG_WITH(jpeg-lib,
+[AS_HELP_STRING(--with-jpeg-lib=DIR,Specify libjpeg library directory.)],
+[JPEG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${JPEG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${JPEG_LDFLAGS}"
+
+AC_CHECK_HEADERS([jpeglib.h],
+  [PSASTRO_CFLAGS="$PSASTRO_CFLAGS $JPEG_CFLAGS" AC_SUBST(JPEG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg headers not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,
+  [PSASTRO_LIBS="$PSASTRO_LIBS $JPEG_LDFLAGS -ljpeg"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg library not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libpng options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(png,
+[AS_HELP_STRING(--with-png=DIR,Specify location of libpng.)],
+[PNG_CFLAGS="-I$withval/include"
+ PNG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(png-include,
+[AS_HELP_STRING(--with-png-include=DIR,Specify libpng include directory.)],
+[PNG_CFLAGS="-I$withval"])
+AC_ARG_WITH(png-lib,
+[AS_HELP_STRING(--with-png-lib=DIR,Specify libpng library directory.)],
+[PNG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${PNG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${PNG_LDFLAGS}"
+
+AC_CHECK_HEADERS([png.h],
+  [PSASTRO_CFLAGS="$PSASTRO_CFLAGS $PNG_CFLAGS" AC_SUBST(PNG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng headers not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+AC_CHECK_LIB(png,png_init_io,
+  [PSASTRO_LIBS="$PSASTRO_LIBS $PNG_LDFLAGS -lpng"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng library not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ use kapa or not? ---------------------
+
+if test "$HAVE_KAPA" == "true" ; then
+  AC_MSG_RESULT([including plotting functions])
+  AC_DEFINE([HAVE_KAPA],[1],[enable use of libkapa])
+else
+  AC_MSG_RESULT([skipping plotting functions])
+  AC_DEFINE([HAVE_KAPA],[0],[disable use of libkapa])
+fi
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror -std=c99"
+echo "PSASTRO_CFLAGS: $PSASTRO_CFLAGS"
+echo "PSASTRO_LIBS: $PSASTRO_LIBS"
+
+AC_SUBST([PSASTRO_CFLAGS])
+AC_SUBST([PSASTRO_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  psastro.pc
+])
+
+AC_OUTPUT
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/doc/mktest.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/doc/mktest.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/doc/mktest.txt	(revision 21632)
@@ -0,0 +1,31 @@
+
+the program, psastro-mktest, will generate a set of fake test data for
+psastro.  the config file 'psastro-mktest.conf' gives an example setup
+for this program.  It generates 4 output files:
+
+- cmpfile : this is a basic elixir-style cmp file (stars plus header)
+  which can be taken as input to psastro
+
+- catfile : this is a basic text reference catalog, which the current
+  psastro loads as a reference
+
+- rawfile : this is a text listing of all astrometry stages for the
+  generated stars: readout, cell, chip, fpa, tpa, sky, with the last
+  two columns being the magnitudes.  this file is generated by
+  applying the transformations starting at the readout and going
+  step-by-step to the sky.
+
+- reffile : this is the inverse of the preceeding file, with the
+  resulting sources transformed from the sky coordinates down
+  step-by-step to the readout pixels.  If the transformations are
+  functioning, these two files (rawfile & reffile) should be identical
+  within floating point errors.
+
+examples:
+
+build a fake dataset
+# psastro-mktest doc/psastro-mktest.conf test.cmp test.cat test.raw test.ref
+
+run psastrom on the fake data:
+# psastro doc/psastro.conf test.cmp test.dat
+
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/doc/notes.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/doc/notes.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/doc/notes.txt	(revision 21632)
@@ -0,0 +1,39 @@
+
+2006.12.26
+
+ work still to be done for psastro:
+
+ - test suite
+   - create a DVO database from a fake population
+   - create fake images with range of errors drawn from DVO
+
+ - 
+
+
+we have a few different astrometry circumstances for our complete
+image/chip hierarchy structure:
+
+- ChipAstrom with one readout, one cell per chip:
+
+  whether or not there is more than one chip, we need to have two
+  stages
+  
+    - in the first stage, the per-chip results are applied to the
+      chip.toFPA parameter set.  
+
+    - in the second stage, the chip results are collectively used to
+      modify the fpa.toSky terms.
+
+      - the average offsets of the chip positions relative to starting
+	coordinates are used to calculate a single average offset to
+	the boresite. 
+
+      - the average rotations of the chips relative to their starting
+        rotations are used to calculate a single average rotation of
+        the boresite
+
+
+- ChipAstrom with more than one readout and/or cell:
+
+  - match all stars up relative to first readout?
+   
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/doc/psastro-mktest.conf
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/doc/psastro-mktest.conf	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/doc/psastro-mktest.conf	(revision 21632)
@@ -0,0 +1,28 @@
+
+NX            S32  2000    	 # chip dimensions
+NY            S32  2000    	 # chip dimensions
+
+NSTARS        S32  20    	 # number of fake stars to generate
+
+# the current test output file only has the basic WCS info.  
+READOUT.COL.0    F32  0		 # cell coord for readout (0,0)
+READOUT.ROW.0    F32  0		 # cell coord for readout (0,0)
+READOUT.COL.BIN  F32  1		 # readout col binning factor
+READOUT.ROW.BIN  F32  1		 # readout row binning factor
+
+CELL.COL.0    	 F32  0		 # chip coord for cell (0,0)
+CELL.ROW.0    	 F32  0		 # chip coord for cell (0,0)
+
+CHIP.COL.0    	 F32  0		 # FPA coord for chip (0,0)
+CHIP.ROW.0    	 F32  0		 # FPA coord for chip (0,0)
+CHIP.THETA    	 F32  0	         # cw rotation from chip to FPA
+CHIP.PARITY.X 	 F32  1		 # x flip 
+CHIP.PARITY.Y 	 F32  1		 # y flip 
+
+FPA.THETA     	 F32  0		 # fpa row offset
+FPA.COL.0  	 F32  0		 # TPA coord for FPA (0,0)
+FPA.ROW.0  	 F32  0		 # TPA coord for FPA (0,0)
+
+PLATE.SCALE	 F32  1	         # plate scale in arcsec/pixel
+RA		 F32  10	 # celestial coord for TPA (0,0)
+DEC     	 F32  20	 # celestial coord for TPA (0,0)
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/doc/psastro.conf
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/doc/psastro.conf	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/doc/psastro.conf	(revision 21632)
@@ -0,0 +1,17 @@
+
+PSASTRO.MATCH.RADIUS 	F32 2.0    # fitting radius in pixels
+
+PSASTRO.CHIP.NX      	S32 1      # chip-to-fpa tranformation order
+PSASTRO.CHIP.NY	     	S32 1      # chip-to-fpa tranformation order
+
+PSASTRO.CHIP.NSIGMA  	F32 3.0    # Nsigma clip for matched-fit 
+PSASTRO.CHIP.NITER   	S32 3      # number of clipping interations for matched-fit 
+
+PSASTRO.GRID.OFFSET  	F32 100.0  # maximum allowed offset for grid match
+PSASTRO.GRID.SCALE   	F32  50.0  # binning scale for grid match
+
+PSASTRO.GRID.MIN.ANGLE  F32   0.0  # starting angle
+PSASTRO.GRID.MAX.ANGLE  F32   0.0  # ending angle
+PSASTRO.GRID.DEL.ANGLE  F32   0.5  # steps between angle attempts
+
+PSASTRO.STARS.MAX       S32 300    # use no more than this number of sources
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/psastro.pc.in
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/psastro.pc.in	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/psastro.pc.in	(revision 21632)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libpsastro
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lpsastro
+Libs.private: @PSASTRO_LIBS@
+Cflags: -I${includedir} @PSASTRO_CFLAGS@
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/.cvsignore
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/.cvsignore	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/.cvsignore	(revision 21632)
@@ -0,0 +1,14 @@
+*.o
+*.lo
+.libs
+.deps
+Makefile
+Makefile.in
+psastro
+psastro.loT
+psastroErrorCodes.h
+psastroErrorCodes.c
+libpsastro.la
+config.h
+config.h.in
+stamp-h1
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/Makefile.am
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/Makefile.am	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/Makefile.am	(revision 21632)
@@ -0,0 +1,60 @@
+
+lib_LTLIBRARIES = libpsastro.la
+libpsastro_la_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSASTRO_CFLAGS)
+
+bin_PROGRAMS = psastro
+psastro_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSASTRO_CFLAGS)
+psastro_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSASTRO_LIBS)
+psastro_LDADD = libpsastro.la
+
+psastro_SOURCES = \
+	psastro.c		
+
+libpsastro_la_SOURCES = \
+psastroArguments.c	    \
+psastroErrorCodes.c         \
+psastroVersion.c            \
+psastroCleanup.c            \
+psastroParseCamera.c   	    \
+psastroDataLoad.c           \
+psastroDataSave.c           \
+psastroAstromGuess.c        \
+psastroLoadRefstars.c       \
+psastroChooseRefstars.c     \
+psastroConvert.c	    \
+psastroChipAstrom.c         \
+psastroOneChip.c	    \
+psastroUtils.c	       	    \
+psastroTestFuncs.c          \
+psastroLuminosityFunction.c \
+psastroRefstarSubset.c      \
+psastroMosaicAstrom.c       \
+psastroMosaicGradients.c    \
+psastroMosaicChipAstrom.c   \
+psastroMosaicOneChip.c      \
+psastroMosaicSetAstrom.c    \
+psastroMosaicSetMatch.c     \
+psastroDemoDump.c           \
+psastroDemoPlot.c
+
+include_HEADERS = \
+	psastro.h \
+	psastroErrorCodes.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+### Error codes.
+BUILT_SOURCES = psastroErrorCodes.h psastroErrorCodes.c
+CLEANFILES = psastroErrorCodes.h psastroErrorCodes.c
+EXTRA_DIST = psastroErrorCodes.dat psastroErrorCodes.h.in psastroErrorCodes.c.in
+
+psastroErrorCodes.h : psastroErrorCodes.dat psastroErrorCodes.h.in
+	$(ERRORCODES) --data=psastroErrorCodes.dat --outdir=. psastroErrorCodes.h
+
+psastroErrorCodes.c : psastroErrorCodes.dat psastroErrorCodes.c.in psastroErrorCodes.h
+	$(ERRORCODES) --data=psastroErrorCodes.dat --outdir=. psastroErrorCodes.c
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastro.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastro.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastro.c	(revision 21632)
@@ -0,0 +1,90 @@
+# include "psastro.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: psastro [-file image(s)] [-list imagelist] (output)\n");
+    exit (2);
+}
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+
+    psastroErrorRegister();              // register our error codes/messages
+
+    // model inits are needed in pmSourceIO
+    // models defined in psphot/src/models are not available in psastro
+    pmModelGroupInit ();
+
+    // load configuration information
+    pmConfig *config = psastroArguments (argc, argv);
+    if (!config) usage ();
+
+    // load identify the data sources
+    if (!psastroParseCamera (config)) {
+	psErrorStackPrint(stderr, "error setting up the camera\n");
+	exit (1);
+    }
+
+    // load the raw pixel data (from PSPHOT.SOURCES)
+    // select subset of stars for astrometry
+    if (!psastroDataLoad (config)) {
+	psErrorStackPrint(stderr, "error loading input data\n");
+	exit (1);
+    }
+
+    // interpret the available initial astrometric information
+    // apply the initial guess
+    if (!psastroAstromGuess (config)) {
+	psErrorStackPrint(stderr, "failed to determine initial astrometry guess\n");
+	exit (1);
+    }
+
+    // load the reference stars overlapping the data stars
+    psArray *refs = psastroLoadRefstars (config);
+    if (!refs) {
+	psErrorStackPrint(stderr, "failed to load reference data\n");
+	exit (1);
+    }
+
+    // choose reference stars corresponding to the selected chips
+    if (!psastroChooseRefstars (config, refs)) {
+	psErrorStackPrint(stderr, "failed to select reference data for chips\n");
+	exit (1);
+    }
+
+    // XXX does this check the recipe??
+    bool chipastro = psMetadataLookupBool (NULL, config->arguments, "PSASTRO.CHIP.MODE");
+    bool mosastro  = psMetadataLookupBool (NULL, config->arguments, "PSASTRO.MOSAIC.MODE");
+    if (!chipastro && !mosastro) {
+	psLogMsg ("psastro", 3, "no astrometry mode selected, assuming chip mode\n");
+	chipastro= true;
+    }
+
+    if (chipastro) {
+	if (!psastroChipAstrom (config, refs)) {
+	    psErrorStackPrint(stderr, "failed to perform single chip astrometry\n");
+	    exit (1);
+	}
+    }
+
+    if (mosastro) {
+	if (!psastroMosaicAstrom (config, refs)) {
+	    psErrorStackPrint(stderr, "failed to perform mosaic camera astrometry\n");
+	    exit (1);
+	}
+    }
+
+    // XXX how do we specify stack astrometry?
+    // psastroStackAstrom (config, refs);
+
+    // write out the results
+    if (!psastroDataSave (config)) {
+	psErrorStackPrint(stderr, "failed to write out data\n");
+	exit (1);
+    }
+
+    psLogMsg ("psastro", 3, "complete psastro run: %f sec\n", psTimerMark ("complete"));
+
+    psastroCleanup (config, refs);
+    exit (EXIT_SUCCESS);
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastro.h
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastro.h	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastro.h	(revision 21632)
@@ -0,0 +1,82 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+# include <stdio.h>
+# include <string.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include <psmodules.h>
+
+# include "psastroErrorCodes.h"
+#define PSASTRO_RECIPE "PSASTRO" // Name of the recipe to use
+
+# define psMemCopy(A)(psMemIncrRefCounter((A)))
+# define DEG_RAD 57.295779513082322
+# define RAD_DEG  0.017453292519943
+# define SIGN(X)  (((X) == 0) ? 0 : ((fabs((double)(X))) / (X)))
+
+// this structure represents a fit to the logN / logS curve for a set of stars
+// logN = offset + slope * logS
+typedef struct {
+  double mMin;
+  double mMax;
+  double offset;
+  double slope;
+} pmLumFunc;
+
+pmConfig         *psastroArguments (int argc, char **argv);
+void              psastroCleanup (pmConfig *config, psArray *refs);
+bool              psastroParseCamera (pmConfig *config);
+bool              psastroDataLoad (pmConfig *config);
+bool              psastroDataSave (pmConfig *config);
+
+bool              psastroConvertFPA (pmFPA *fpa, psMetadata *recipe);
+bool              psastroConvertReadout (pmReadout *readout, psMetadata *recipe);
+psArray          *pmSourceToAstromObj (psArray *sources);
+bool              psastroAstromGuess (pmConfig *config);
+
+// bool              pmAstromReadWCS (pmFPA *fpa, pmChip *chip, psMetadata *header, double plateScale, bool isMosaic);
+// bool              pmAstromWriteWCS (psPlaneTransform *toFPA, psPlaneDistort *toTPA, psProjection *toSky, psMetadata *header, double plateScale);
+psPlaneDistort   *psPlaneDistortIdentity ();
+bool psPlaneDistortIsIdentity (psPlaneDistort *distort);
+
+psArray          *psastroLoadRefstars (pmConfig *config);
+bool              psastroChipAstrom (pmConfig *config, psArray *refs);
+bool              psastroOneChip (pmFPA *fpa, pmChip *chip, psArray *refset, psArray *rawset, psMetadata *recipe, psMetadata *updates);
+bool              psastroChooseRefstars (pmConfig *config, psArray *refs);
+bool              psastroRefstarSubset (pmReadout *readout);
+pmLumFunc        *psastroLuminosityFunction (psArray *stars);
+
+// utility functions:
+bool              psastroUpdateChipToFPA (pmFPA *fpa, pmChip *chip, psArray *rawstars, psArray *refstars);
+bool              psastroWriteStars (char *filename, psArray *sources);
+bool              psastroWriteTransform (psPlaneTransform *map);
+
+// mosaic fitting functions
+bool psastroMosaicGradients (pmFPA *fpa, psMetadata *recipe);
+bool psastroMosaicCommonScale (pmFPA *fpa, psMetadata *recipe);
+bool psastroMosaicAstrom (pmConfig *config, psArray *refs);
+bool psastroMosaicChipAstrom (pmFPA *fpa, psMetadata *recipe, bool nonlinear);
+bool psastroMosaicSetMatch (pmFPA *fpa, psMetadata *recipe, int iteration);
+bool psastroMosaicSetAstrom (pmFPA *fpa);
+bool psastroMosaicHeaders (pmConfig *config);
+bool psastroMosaicRescaleChips (pmFPA *fpa);
+bool psastroMosaicOneChip (pmChip *chip, pmReadout *readout, psMetadata *recipe, psMetadata *updates, bool nonlinear);
+
+// Return version strings.
+psString psastroVersion(void);
+psString psastroVersionLong(void);
+
+// demo plots
+bool psastroPlotRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip);
+bool psastroPlotRefstars (psArray *refstars);
+bool psastroPlotOneChipFit (psArray *rawstars, psArray *refstars, psArray *match, pmAstromFitResults *results);
+
+bool psastroDumpRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip);
+bool psastroDumpRefstars (psArray *refstars, char *filename);
+bool psastroDumpMatches (pmFPA *fpa, char *filename);
+
+bool psastroMosaicSetAstrom_tmp (pmFPA *fpa);
+int psastroSortByMag (const void **a, const void **b);
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroArguments.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroArguments.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroArguments.c	(revision 21632)
@@ -0,0 +1,77 @@
+# include "psastro.h"
+
+pmConfig *psastroArguments (int argc, char **argv) {
+
+    bool status;
+    int N;
+
+    if (argc == 1) {
+	psError(PSASTRO_ERR_ARGUMENTS, true, "No arguments supplied");
+	return NULL;
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSASTRO_RECIPE);
+    if (config == NULL) {
+        psError(PSASTRO_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSASTRO recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSASTRO_RECIPE);
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_META_REPLACE, "", psStringCopy(argv[N]));
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // apply mosastro mode?
+    if ((N = psArgumentGet (argc, argv, "-mosastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.MOSAIC.MODE", PS_META_REPLACE, "", false);
+    }
+    if ((N = psArgumentGet (argc, argv, "+mosastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.MOSAIC.MODE", PS_META_REPLACE, "", true);
+    }
+
+    // apply chipastro mode?
+    if ((N = psArgumentGet (argc, argv, "-chipastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.CHIP.MODE", PS_META_REPLACE, "", false);
+    }
+    if ((N = psArgumentGet (argc, argv, "+chipastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.CHIP.MODE", PS_META_REPLACE, "", true);
+    }
+
+    // drop the local view on the options (saved in config->arguments)
+    psFree (options);
+
+    status = pmConfigFileSetsMD (config->arguments, config, "INPUT", "-file", "-list");
+    if (!status) {
+	psError(PSASTRO_ERR_ARGUMENTS, true, "Missing -file (input) or -list (input)");
+	return NULL;
+    }
+    
+    if (argc != 2) {
+	psError(PSASTRO_ERR_ARGUMENTS, true, "Incorrect arguments supplied");
+	return NULL;
+    }
+    
+    // output positions is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psastro", 1, "Done with psastroArguments...\n");
+    return (config);
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroAstromGuess.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroAstromGuess.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroAstromGuess.c	(revision 21632)
@@ -0,0 +1,127 @@
+# include "psastro.h"
+
+// this function loads the header WCS astrometry terms into the fpa terms and applies the
+// astrometry to the detected objects.
+
+// this function assumes the initial astrometry arrives in the form of WCS keywords in the
+// headers corresponding to the chips.
+
+bool psastroAstromGuess (pmConfig *config) {
+
+    bool newFPA = true;
+    bool status = false;
+    double RAmin = FLT_MAX;
+    double RAmax = FLT_MIN;
+    double DECmin = FLT_MAX;
+    double DECmax = FLT_MIN;
+
+    double RAminSky = NAN;
+    double RAmaxSky = NAN;
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+	return false;
+    }
+
+    // physical pixel scale in microns per pixel
+    double pixelScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.SCALE");
+    if (!status) {
+	psError(PS_ERR_IO, false, "Failed to lookup pixel scale"); 
+	return false; 
+    } 
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPA *fpa = input->fpa;
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
+
+        pmAstromReadWCS (fpa, chip, hdu->header, pixelScale);
+        if (newFPA) {
+            newFPA = false;
+	    while (fpa->toSky->R <        0) fpa->toSky->R += 2.0*M_PI;
+	    while (fpa->toSky->R > 2.0*M_PI) fpa->toSky->R -= 2.0*M_PI;
+            RAminSky = fpa->toSky->R - M_PI;
+            RAmaxSky = fpa->toSky->R + M_PI;
+        }
+
+        // apply the new WCS guess data to all of the data in the readouts
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+                if (rawstars == NULL) { continue; }
+
+                for (int i = 0; i < rawstars->n; i++) {
+                    pmAstromObj *raw = rawstars->data[i];
+
+                    psPlaneTransformApply (raw->FP, chip->toFPA, raw->chip);
+                    psPlaneTransformApply (raw->TP, fpa->toTPA, raw->FP);
+                    psDeproject (raw->sky, raw->TP, fpa->toSky);
+
+                    // rationalize ra to sky range centered on boresite
+                    while (raw->sky->r < RAminSky) raw->sky->r += 2.0*M_PI;
+                    while (raw->sky->r > RAmaxSky) raw->sky->r -= 2.0*M_PI;
+
+                    RAmin = PS_MIN (raw->sky->r, RAmin);
+                    RAmax = PS_MAX (raw->sky->r, RAmax);
+
+                    DECmin = PS_MIN (raw->sky->d, DECmin);
+                    DECmax = PS_MAX (raw->sky->d, DECmax);
+                }
+
+		// dump or plot the resulting projected positions
+		if (psTraceGetLevel("psastro.dump") > 0) {
+		    psastroDumpRawstars (rawstars, fpa, chip);
+		}
+
+		if (psTraceGetLevel("psastro.plot") > 0) {
+		    psastroPlotRawstars (rawstars, fpa, chip);
+		}
+            }
+        }
+    }
+
+    psLogMsg ("psastro", 2, "loaded raw data from %f,%f to %f,%f\n", 
+	      DEG_RAD*RAmin, DEG_RAD*DECmin, 
+	      DEG_RAD*RAmax, DEG_RAD*DECmax);
+
+    psMetadataAdd (recipe, PS_LIST_TAIL, "RA_MIN",  PS_DATA_F32 | PS_META_REPLACE, "", RAmin);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "RA_MAX",  PS_DATA_F32 | PS_META_REPLACE, "", RAmax);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "DEC_MIN", PS_DATA_F32 | PS_META_REPLACE, "", DECmin);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "DEC_MAX", PS_DATA_F32 | PS_META_REPLACE, "", DECmax);
+
+    psFree (view);
+    return true;
+}
+
+/* coordinate frame hierachy
+   pixels (on a given readout)
+   cell
+   chip
+   FP (focal plane)
+   TP (tangent plane)
+   sky (ra, dec)
+*/
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroChipAstrom.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroChipAstrom.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroChipAstrom.c	(revision 21632)
@@ -0,0 +1,80 @@
+# include "psastro.h"
+# define NONLIN_TOL 0.001 /* tolerance in pixels */
+
+bool psastroChipAstrom (pmConfig *config, psArray *refs) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPA *fpa = input->fpa;
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		// the absolute minimum number of stars is 4 (for order = 1)
+		if ((rawstars->n < 4) || (refstars->n < 4)) {
+		    psLogMsg ("psastro", 3, "insufficient rawstars (%ld) or refstars (%ld)", 
+			    rawstars->n, refstars->n);
+		    continue;
+		} 
+
+		// save WCS and analysis metadata in update header
+		psMetadata *updates = psMetadataAlloc();
+
+		// XXX update the header with info to reflect the failure
+		if (!psastroOneChip (fpa, chip, refstars, rawstars, recipe, updates)) {
+		    psLogMsg ("psastro", 3, "failed to find a solution\n");
+		    psFree (updates);
+		    continue;
+		}
+		pmAstromWriteWCS (updates, fpa, chip, NONLIN_TOL);
+		psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_DATA_METADATA, "psastro header stats", updates);
+		psFree (updates);
+	    }
+	}
+    }
+    psFree (view);
+    return true;
+}
+
+/* coordinate frame hierachy
+   pixels (on a given readout)
+   cell
+   chip
+   FP (focal plane)
+   TP (tangent plane)
+   sky (ra, dec)
+*/
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroChooseRefstars.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroChooseRefstars.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroChooseRefstars.c	(revision 21632)
@@ -0,0 +1,94 @@
+# include "psastro.h"
+
+bool psastroChooseRefstars (pmConfig *config, psArray *refs) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+        return false;
+    }
+
+    float fieldExtra = psMetadataLookupS32 (&status, recipe, "PSASTRO.FIELD.EXTRA");
+    if (!status) fieldExtra = 0.0;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPA *fpa = input->fpa;
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            // process each of the readouts
+            // XXX there can only be one readout per chip in astrometry, right?
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                // read WCS data from the corresponding header
+                pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
+
+                int Nx = psMetadataLookupS32 (&status, hdu->header, "NAXIS1");
+                int Ny = psMetadataLookupS32 (&status, hdu->header, "NAXIS2");
+
+                float minX = -fieldExtra*Nx;
+                float maxX = (1+fieldExtra)*Nx;
+                float minY = -fieldExtra*Ny;
+                float maxY = (1+fieldExtra)*Ny;
+
+                // the refstars is a subset within range of this chip
+                psArray *refstars = psArrayAllocEmpty (100);
+
+                // select the reference objects within range of this readout
+                // project the reference objects to this chip
+                for (int i = 0; i < refs->n; i++) {
+                    pmAstromObj *ref = pmAstromObjCopy(refs->data[i]);
+
+                    psProject (ref->TP, ref->sky, fpa->toSky);
+                    psPlaneTransformApply (ref->FP, fpa->fromTPA, ref->TP);
+                    psPlaneTransformApply (ref->chip, chip->fromFPA, ref->FP);
+
+                    // limit the X,Y range of the refs to the selected chip
+                    if (ref->chip->x < minX) goto skip;
+                    if (ref->chip->x > maxX) goto skip;
+                    if (ref->chip->y < minY) goto skip;
+                    if (ref->chip->y > maxY) goto skip;
+
+                    psArrayAdd (refstars, 100, ref);
+                skip:
+                    psFree (ref);
+                }
+                psTrace ("psastro", 4, "Added %ld refstars\n", refstars->n);
+
+                psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.REFSTARS", PS_DATA_ARRAY, "astrometry matches", refstars);
+                psFree (refstars);
+
+		// XXX this error means the readout fails, but should probably not kill the entire program
+		// in this case, no PSASTRO.REFSTARS is added to readout->analysis
+                if (!psastroRefstarSubset (readout)) {
+		    psError(PSASTRO_ERR_DATA, false, "Can't determine an appropriate refstar subset\n");
+		    return false;
+		}
+            }
+        }
+    }
+
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroCleanup.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroCleanup.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroCleanup.c	(revision 21632)
@@ -0,0 +1,18 @@
+# include "psastro.h"
+
+void psastroCleanup (pmConfig *config, psArray *refs) {
+
+    psFree (refs);
+    psFree (config);
+
+    psTimerStop ();
+    psMemCheckCorruption (stderr, true);
+    pmModelGroupCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psastro");
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "psastro");
+
+    return;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroConvert.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroConvert.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroConvert.c	(revision 21632)
@@ -0,0 +1,115 @@
+# include "psastro.h"
+// leak free 2006.04.27
+
+bool psastroConvertFPA (pmFPA *fpa, psMetadata *recipe) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                psastroConvertReadout (readout, recipe);
+            }
+        }
+    }
+    psFree (view);
+    return true;
+}
+
+// pass/apply the WCS information?
+bool psastroConvertReadout (pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+
+    // PSPHOT.SOURCES carries the pmSource objects (from psphot analysis or loaded externally)
+    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (sources == NULL)
+        return false;
+
+    // convert the pmSource objects into pmAstromObj objects (drop !STAR and SATSTAR?)
+    psArray *inStars = pmSourceToAstromObj (sources);
+
+    // sort in ascending magnitude order
+    psArraySort (inStars, psastroSortByMag);
+
+    // we are going to select the brighter Nmax subset for astrometry 
+    int nMax = psMetadataLookupS32 (&status, recipe, "PSASTRO.MAX.NSTAR");
+    if (!status || (nMax < 10)) nMax = 300; // 10 is really somewhat absurd as a lower limit
+
+    // choose the first nMax sources
+    psArray *rawStars = psArrayAlloc (PS_MIN (nMax, inStars->n));
+    for (int i = 0; i < rawStars->n; i++) {
+	rawStars->data[i] = psMemIncrRefCounter (inStars->data[i]);
+    }
+
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.RAWSTARS", PS_DATA_ARRAY, "astrometry objects", rawStars);
+    psLogMsg ("psastro", 4, "loaded %ld sources, using %ld of %ld good sources\n", sources->n, rawStars->n, inStars->n);
+
+    psFree (inStars);
+    psFree (rawStars);
+
+    return true;
+}
+
+// select a magnitude range?
+psArray *pmSourceToAstromObj (psArray *sources) {
+
+    psArray *objects = psArrayAllocEmpty (sources->n);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // only accept the PSF sources?
+	// XXX drop SATSTAR?
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+
+        pmModel *model = source->modelPSF;
+        if (model == NULL) continue;
+
+        psF32 *PAR = model->params->data.F32;
+
+        pmAstromObj *obj = pmAstromObjAlloc ();
+
+        // is the source magnitude calibrated in any sense?
+        obj->pix->x = PAR[2];
+        obj->pix->y = PAR[3];
+        obj->Mag = source->psfMag;
+
+        // XXX do we have the information giving the readout and cell offset?
+        // for the moment, assume chip == cell == readout
+        *obj->cell = *obj->pix;
+        *obj->chip = *obj->cell;
+
+        psArrayAdd (objects, 100, obj);
+        psFree (obj);
+    }
+    return objects;
+}
+
+// sort by Mag (ascending)
+int psastroSortByMag (const void **a, const void **b)
+{
+    pmAstromObj *A = *(pmAstromObj **) a;
+    pmAstromObj *B = *(pmAstromObj **) b;
+
+    psF32 mA = (isfinite(A->Mag)) ? A->Mag : FLT_MAX;
+    psF32 mB = (isfinite(B->Mag)) ? B->Mag : FLT_MAX;
+
+    psF32 diff = mA - mB;
+    if (diff > FLT_EPSILON) return (+1);
+    if (diff < FLT_EPSILON) return (-1);
+    return (0);
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDataLoad.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDataLoad.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDataLoad.c	(revision 21632)
@@ -0,0 +1,64 @@
+# include "psastro.h"
+// this loop loads the data from the input files and selects the
+// brighter stars for astrometry
+// at the end of this function, the complete stellar data is loaded
+// into the correct fpa structure locations (readout.analysis:PSPHOT.SOURCES)
+
+// all of the different astrometry analysis modes use the same data load loop
+bool psastroDataLoad (pmConfig *config) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+	return false;
+    }
+
+    // de-activate all files except PSASTRO.INPUT
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.INPUT");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // files associated with the science image
+    pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+	    pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+		if (! readout->data_exists) { continue; }
+
+		psastroConvertReadout (readout, recipe);
+
+		pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+	    }
+	    pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+	}
+	pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+    }
+    pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+    psFree (view);
+    return true;
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDataSave.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDataSave.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDataSave.c	(revision 21632)
@@ -0,0 +1,63 @@
+# include "psastro.h"
+// XXX leak free 2006.04.27
+
+// this loop saves the photometry/astrometry data files
+bool psastroDataSave (pmConfig *config) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    // select the output data sources
+    pmFPAfile *output = psMetadataLookupPtr (NULL, config->files, "PSASTRO.OUTPUT");
+    if (!output) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find or interpret output file rule PSASTRO.OUTPUT!\n");
+	return false;
+    }
+
+    // de-activate all files except PSASTRO.OUTPUT
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUTPUT");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // open/load files as needed
+    pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+    while ((chip = pmFPAviewNextChip (view, output->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+	while ((cell = pmFPAviewNextCell (view, output->fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+	    pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, output->fpa, 1)) != NULL) {
+		pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+		if (! readout->data_exists) { continue; }
+
+		pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+	    }
+	    pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+	}
+	pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+    }
+    pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+
+    // activate all files except PSASTRO.OUTPUT
+    pmFPAfileActivate (config->files, true, NULL);
+    pmFPAfileActivate (config->files, false, "PSASTRO.OUTPUT");
+
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDemoDump.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDemoDump.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDemoDump.c	(revision 21632)
@@ -0,0 +1,119 @@
+# include "psastro.h"
+
+// this function is used for test purposes (-trace psastro.dump.psastroAstromGuess 1)
+bool psastroDumpRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip) {
+
+    FILE *f1 = fopen ("rawstars.dat", "w");
+    FILE *f2 = fopen ("rawstars.down.dat", "w");
+
+    for (int i = 0; i < rawstars->n; i++) {
+	pmAstromObj *raw = rawstars->data[i];
+
+	psPlane *fp = psPlaneAlloc();
+	psPlane *tp = psPlaneAlloc();
+	psPlane *ch = psPlaneAlloc();
+			
+	psProject (tp, raw->sky, fpa->toSky);
+	psPlaneTransformApply (fp, fpa->fromTPA, tp);
+	psPlaneTransformApply (ch, chip->fromFPA, fp);
+			
+	// write out the upward projections
+	fprintf (f1, "%d  %f %f  %f  %f %f  %f %f  %f %f\n", i,
+		 raw->sky->r, raw->sky->d, raw->Mag, 
+		 raw->TP->x, raw->TP->y, 
+		 raw->FP->x, raw->FP->y, 
+		 raw->chip->x, raw->chip->y);
+		
+	// write out the downward projections
+	fprintf (f2, "%d  %f %f  %f  %f %f  %f %f  %f %f\n", i,
+		 raw->sky->r, raw->sky->d, raw->Mag, 
+		 tp->x, tp->y, 
+		 fp->x, fp->y, 
+		 ch->x, ch->y);
+		
+	psFree (fp);
+	psFree (tp);
+	psFree (ch);
+    }
+
+    fclose (f1);
+    fclose (f2);
+    return true;
+}
+
+// this function is used for test purposes (-trace psastro.dump.psastroLoadRefstars 1)
+bool psastroDumpRefstars (psArray *refstars, char *filename) {
+
+    FILE *f = fopen (filename, "w");
+
+    for (int i = 0; i < refstars->n; i++) {
+	pmAstromObj *ref = refstars->data[i];
+
+	// write out the refstar data
+	fprintf (f, "%d  %f %f  %f\n", i,
+		 ref->sky->r, ref->sky->d, ref->Mag);
+    }
+
+    fclose (f);
+    return true;
+}
+
+bool psastroDumpMatches (pmFPA *fpa, char *filename) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    FILE *f = fopen (filename, "w");
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) continue;
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) continue;
+
+	    // process each of the readouts
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) continue;
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) continue;
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) continue;
+		psTrace ("psastro", 4, "Trying %ld refstars\n", refstars->n);
+
+		psArray *matches = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+		if (matches == NULL) continue;
+
+		for (int i = 0; i < matches->n; i++) {
+		    pmAstromMatch *match = matches->data[i];
+
+		    pmAstromObj *raw = rawstars->data[match->raw];
+		    fprintf (f, "%f %f  %f %f  %f %f  %f %f  %f   |   ",  
+			     DEG_RAD*raw->sky->r, DEG_RAD*raw->sky->d, 
+			     raw->TP->x, raw->TP->y, 
+			     raw->FP->x, raw->FP->y, 
+			     raw->chip->x, raw->chip->y, raw->Mag);
+
+		    pmAstromObj *ref = refstars->data[match->ref];
+		    fprintf (f, "%f %f  %f %f  %f %f  %f %f  %f\n", 
+			     DEG_RAD*ref->sky->r, DEG_RAD*ref->sky->d, 
+			     ref->TP->x, ref->TP->y, 
+			     ref->FP->x, ref->FP->y, 
+			     ref->chip->x, ref->chip->y, ref->Mag);
+		}
+	    }
+	}
+    }
+    fclose (f);
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDemoPlot.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDemoPlot.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroDemoPlot.c	(revision 21632)
@@ -0,0 +1,336 @@
+# include "psastro.h"
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+
+bool psastroPlotRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip)
+{
+    Graphdata graphdata;
+    KapaSection section;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    KapaResize (kapa, 1000, 1000);
+    KapaInitGraph (&graphdata);
+    KapaClear (kapa, TRUE);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 7;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+
+    section.dx = 0.5;
+    section.dy = 0.5;
+
+    psVector *xVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    psVector *zVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+
+    section.x = 0.0;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a0");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    int n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+	pmAstromObj *raw = rawstars->data[i];
+	if (!isfinite(raw->Mag)) continue;
+	xVec->data.F32[n] = raw->chip->x;
+	yVec->data.F32[n] = raw->chip->y;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    section.x = 0.5;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a1");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+	pmAstromObj *raw = rawstars->data[i];
+	if (!isfinite(raw->Mag)) continue;
+	xVec->data.F32[n] = raw->FP->x;
+	yVec->data.F32[n] = raw->FP->y;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    section.x = 0.0;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a2");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+	pmAstromObj *raw = rawstars->data[i];
+	if (!isfinite(raw->Mag)) continue;
+	xVec->data.F32[n] = raw->TP->x;
+	yVec->data.F32[n] = raw->TP->y;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    section.x = 0.5;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a3");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+	pmAstromObj *raw = rawstars->data[i];
+	if (!isfinite(raw->Mag)) continue;
+	xVec->data.F32[n] = raw->sky->r;
+	yVec->data.F32[n] = raw->sky->d;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // pause and wait for user input: 
+    // continue, save (provide name), ??
+    char key[10], name[80];
+    fprintf (stdout, "(s)ave plot or [c]ontinue? ");
+    fscanf (stdin, "%s", key);
+    fprintf (stderr, "got: %s\n", key);
+    if (key[0] == 's') {
+	fprintf (stdout, "enter plot name [rawstars.png]: ");
+	fscanf (stdin, "%s", name);
+	if (!strcmp (name, "")) strcpy (name, "rawstars.png");
+	KapaPNG (kapa, name);
+    }
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+    return true;
+}
+
+bool psastroPlotRefstars (psArray *refstars)
+{
+    Graphdata graphdata;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    KapaResize (kapa, 1000, 1000);
+    KapaInitGraph (&graphdata);
+    KapaClear (kapa, TRUE);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 7;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+
+    psVector *xVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+    psVector *zVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+
+    int n = 0;
+    for (int i = 0; i < refstars->n; i++) {
+	pmAstromObj *ref = refstars->data[i];
+	if (!isfinite(ref->Mag)) continue;
+	xVec->data.F32[n] = ref->sky->r;
+	yVec->data.F32[n] = ref->sky->d;
+	zVec->data.F32[n] = ref->Mag;
+	n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // pause and wait for user input: 
+    // continue, save (provide name), ??
+    char key[10], name[80];
+    fprintf (stdout, "(s)ave plot or [c]ontinue? ");
+    fscanf (stdin, "%s", key);
+    fprintf (stderr, "got: %s\n", key);
+    if (key[0] == 's') {
+	fprintf (stdout, "enter plot name [refstars.png]: ");
+	fscanf (stdin, "%s", name);
+	if (!strcmp (name, "")) strcpy (name, "refstars.png");
+	KapaPNG (kapa, name);
+    }
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+    return true;
+}
+
+bool psastroPlotOneChipFit (psArray *rawstars, psArray *refstars, psArray *match, pmAstromFitResults *results) {
+
+    Graphdata graphdata;
+    KapaSection section;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    KapaResize (kapa, 1000, 1000);
+    KapaInitGraph (&graphdata);
+    KapaClear (kapa, TRUE);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 7;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+
+    section.dx = 0.5;
+    section.dy = 0.5;
+
+    psVector *xVec = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *zVec = psVectorAlloc (match->n, PS_TYPE_F32);
+
+    // X vs dX
+    section.x = 0.0;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a0");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    int n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+	
+	xVec->data.F32[n] = raw->chip->x;
+	yVec->data.F32[n] = raw->chip->x - ref->chip->x;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }	
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // X vs dY
+    section.x = 0.5;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a1");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+	
+	if (!isfinite(raw->Mag)) continue;
+	xVec->data.F32[n] = raw->chip->x;
+	yVec->data.F32[n] = raw->chip->y - ref->chip->y;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }	
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // Y vs dX
+    section.x = 0.0;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a2");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+	
+	if (!isfinite(raw->Mag)) continue;
+	xVec->data.F32[n] = raw->chip->y;
+	yVec->data.F32[n] = raw->chip->x - ref->chip->x;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }	
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // Y vs dY
+    section.x = 0.5;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a3");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+	
+	if (!isfinite(raw->Mag)) continue;
+	xVec->data.F32[n] = raw->chip->y;
+	yVec->data.F32[n] = raw->chip->y - ref->chip->y;
+	zVec->data.F32[n] = raw->Mag;
+	n++;
+    }	
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // pause and wait for user input: 
+    // continue, save (provide name), ??
+    char key[10], name[80];
+    fprintf (stdout, "(s)ave plot or [c]ontinue? ");
+    fscanf (stdin, "%s", key);
+    fprintf (stderr, "got: %s\n", key);
+    if (key[0] == 's') {
+	fprintf (stdout, "enter plot name [chipfit.png]: ");
+	fscanf (stdin, "%s", name);
+	if (!strcmp (name, "")) strcpy (name, "chipfit.png");
+	KapaPNG (kapa, name);
+    }
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+    return true;
+}
+
+# else
+
+bool psastroPlotRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip)
+{
+    return false;
+}
+
+bool psastroPlotRefstars (psArray *refstars)
+{
+    return false;
+}
+
+bool psastroPlotOneChipFit (psArray *rawstars, psArray *refstars, psArray *match, pmAstromFitResults *results)
+{
+    return false;
+}
+
+# endif
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.c.in
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.c.in	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.c.in	(revision 21632)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PSASTRO_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "psastroErrorCodes.h"
+
+void psastroErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSASTRO_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSASTRO_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSASTRO_ERR_NERROR - PSASTRO_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.dat
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.dat	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.dat	(revision 21632)
@@ -0,0 +1,12 @@
+#
+# This file is used to generate pmErrorClasses.h
+#
+BASE = 300		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+ARGUMENTS		Incorrect arguments
+CONFIG			Problem in configure files
+IO			Problem in FITS I/O
+WCS      		Error interpreting FITS WCS information
+DATA                    Problem in data values
+REFSTARS                Problem in data values
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.h.in
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.h.in	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroErrorCodes.h.in	(revision 21632)
@@ -0,0 +1,18 @@
+#if !defined(PSASTRO_ERROR_CODES_H)
+#define PSASTRO_ERROR_CODES_H
+/*
+ * The line
+ *  PSASTRO_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSASTRO_ERR_BASE = 600,
+    PSASTRO_ERR_${ErrorCode},
+    PSASTRO_ERR_NERROR
+} psastroErrorCode;
+
+void psastroErrorRegister(void);
+
+#endif
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroLoadRefstars.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroLoadRefstars.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroLoadRefstars.c	(revision 21632)
@@ -0,0 +1,124 @@
+# include "psastro.h"
+# define ELIXIR_MODE 1
+
+psArray *psastroLoadRefstars (pmConfig *config) {
+
+    int fd;
+    bool status;
+    char *catformat = NULL;
+    char *getstarLine = NULL;
+
+    // select the DVO database?
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+
+    // DVO APIs expect decimal degrees
+    float RAmin  = DEG_RAD*psMetadataLookupF32(NULL, recipe, "RA_MIN");
+    float RAmax  = DEG_RAD*psMetadataLookupF32(NULL, recipe, "RA_MAX");
+    float DECmin = DEG_RAD*psMetadataLookupF32(NULL, recipe, "DEC_MIN");
+    float DECmax = DEG_RAD*psMetadataLookupF32(NULL, recipe, "DEC_MAX");
+    float MAGmax = psMetadataLookupF32(NULL, recipe, "MAG_MAX");
+
+    char *CATDIR = psMetadataLookupStr(NULL, recipe, "DVO.CATDIR");
+    if (CATDIR == NULL) {
+        psLogMsg ("psastro", 2, "warning: missing DVO.CATDIR, using ~/.ptolemyrc definition\n");
+    }
+
+    // issue the following command:
+    // getstar -region RAmin RAmax DECmin DECmax
+
+    char tempFile[64];
+    sprintf (tempFile, "/tmp/psastro.XXXXXX");
+    if ((fd = mkstemp (tempFile)) == -1) {
+        psError(PSASTRO_ERR_REFSTARS, true, "error creating temp output file\n");
+        return NULL;
+    }
+    close (fd);
+
+    psTimerStart ("psastro");
+
+    // use fork to add timeout capability
+    if (ELIXIR_MODE) {
+        psStringAppend (&catformat, "-D CATFORMAT elixir ");
+    } else {
+        psStringAppend (&catformat, "-D CATFORMAT panstarrs ");
+    }
+
+    if (CATDIR) {
+        psStringAppend (&catformat, "-D CATDIR %s ", CATDIR);
+    }
+
+    // XXX set getstar in config?
+    // psStringAppend (&getstarLine, "/home/kiawe/eugene/psconfig/dev.lin64/bin/getstar %s -D CATMODE mef -maglim %f -region %f %f %f %f -o %s", catformat, MAGmax, RAmin, DECmin, RAmax, DECmax, tempFile);
+    psStringAppend (&getstarLine, "getstar %s -D CATMODE mef -maglim %f -region %f %f %f %f -o %s", catformat, MAGmax, RAmin, DECmin, RAmax, DECmax, tempFile);
+    psTrace ("psastro", 3, "%s\n", getstarLine);
+
+    // XXX use psPipe: catch stderr, stdout, allow for Nsec timeout...
+    status = system (getstarLine);
+    if (status) {
+        psError(PSASTRO_ERR_REFSTARS, true, "error loading reference data\n");
+        return NULL;
+    }
+    psFree (getstarLine);
+    psFree (catformat);
+
+    psLogMsg ("psastro", 3, "ran getstar : %f sec\n", psTimerMark ("psastro"));
+
+    // the output from getstar is a file with the Average table
+    psFits *fits = psFitsOpen (tempFile, "r");
+
+    if (ELIXIR_MODE) {
+        psFitsMoveExtName (fits, "DVO_AVERAGE_ELIXIR");
+    } else {
+        psFitsMoveExtName (fits, "DVO_AVERAGE_PANSTARRS");
+    }
+
+    psTimerStart ("psastro");
+    psMetadata *header = psFitsReadHeader (NULL, fits);
+    psArray *table = psFitsReadTable (fits);
+    psFitsClose (fits);
+
+    unlink (tempFile);
+    psLogMsg ("psastro", 3, "read getstar output table : %f sec\n", psTimerMark ("psastro"));
+
+    // convert the Average table to the pmAstromObj entries
+    psTimerStart ("psastro");
+    psArray *refstars = psArrayAllocEmpty (table->n);
+    for (int i = 0; i < table->n; i++) {
+        pmAstromObj *ref = pmAstromObjAlloc ();
+
+        psMetadata *row = table->data[i];
+
+        // DVO tables are stored in degrees
+        if (ELIXIR_MODE) {
+            ref->sky->r   = RAD_DEG*psMetadataLookupF32 (&status, row, "RA");
+            ref->sky->d   = RAD_DEG*psMetadataLookupF32 (&status, row, "DEC");
+            ref->Mag      = 0.001*psMetadataLookupS32 (&status, row, "MAG");  // ELIXIR uses millimags
+        } else {
+            ref->sky->r   = RAD_DEG*psMetadataLookupF64 (&status, row, "RA");
+            ref->sky->d   = RAD_DEG*psMetadataLookupF64 (&status, row, "DEC");
+            ref->Mag      = psMetadataLookupF32 (&status, row, "MAG"); // PANSTARRS uses mags
+        }
+
+        psArrayAdd (refstars, 100, ref);
+        psFree (ref);
+    }
+    psFree (header);
+    psFree (table);
+    psLogMsg ("psastro", 3, "converted table to pmAstromObj : %f sec\n", psTimerMark ("psastro"));
+
+    psTrace ("psastro", 3, "loaded %ld reference stars from (%10.6f,%10.6f) - (%10.6f,%10.6f)\n",
+             refstars->n, RAmin, DECmin, RAmax, DECmax);
+
+    // dump or plot the available refstars
+    if (psTraceGetLevel("psastro.dump") > 0) {
+	psastroDumpRefstars (refstars, "refstars.dat");
+    }
+
+    if (psTraceGetLevel("psastro.plot") > 0) {
+	psastroPlotRefstars (refstars);
+    }
+
+    return refstars;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroLuminosityFunction.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroLuminosityFunction.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroLuminosityFunction.c	(revision 21632)
@@ -0,0 +1,126 @@
+# include "psastro.h"
+# define dMag 0.1
+// XXX put this in config?
+
+static void pmLumFuncFree (pmLumFunc *func) {
+
+  if (func == NULL) return;
+
+  return;
+}
+
+pmLumFunc *pmLumFuncAlloc (double mMin, double mMax, double offset, double slope) {
+
+  pmLumFunc *func = (pmLumFunc *) psAlloc(sizeof(pmLumFunc));
+  psMemSetDeallocator(func, (psFreeFunc) pmLumFuncFree);
+
+  func->mMin = mMin;
+  func->mMax = mMax;
+  func->slope = slope;
+  func->offset = offset;
+
+  return func;
+}
+
+pmLumFunc *psastroLuminosityFunction (psArray *stars) {
+
+  // determine the max and min magnitude for the array of stars
+  pmAstromObj *star = stars->data[0];
+  double mMin = star->Mag;
+  double mMax = star->Mag;
+  for (int i = 0; i < stars->n; i++) {
+    star = stars->data[i];
+    mMin = PS_MIN (star->Mag, mMin);
+    mMax = PS_MAX (star->Mag, mMax);
+  }
+
+  int nBin = 1 + (mMax - mMin) / dMag;
+  if (nBin <= 1) {
+    psError(PSASTRO_ERR_DATA, true, "magnitude range of 0.0\n");
+    return NULL;
+  }
+
+  // create a histogram of the magnitudes
+  // bin[0] = mMin : mMin + dMag
+  // bin[i] = mMin + i*dMag : mMin + (i+1)*dMag
+  psVector *nMags = psVectorAlloc (nBin, PS_TYPE_F32);
+  psVectorInit (nMags, 0);
+
+  for (int i = 0; i < stars->n; i++) {
+    star = stars->data[i];
+    if (!isfinite(star->Mag)) continue;
+    int bin = (star->Mag - mMin) / dMag;
+    if (bin < 0) psAbort("bin cannot be negative!");
+    if (bin >= nBin) psAbort("bin cannot be > %d!", nBin);
+    nMags->data.F32[bin] += 1.0;
+  }
+
+  // find the peak and position
+  int iPeak = 0;
+  int nPeak = 0;
+  for (int i = 0; i < nMags->n; i++) {
+    if (nMags->data.F32[i] < nPeak) continue;
+    iPeak = i;
+    nPeak = nMags->data.F32[i];
+  }
+
+  psVector *lnMag = psVectorAllocEmpty (nBin, PS_TYPE_F32);
+  psVector *Mag = psVectorAllocEmpty (nBin, PS_TYPE_F32);
+
+  // create 2 vectors represnting the dlogN/dlogS line
+  // exclude bins with no stars
+  // exclude points after the peak with N < 0.8*peak
+  int n = 0;
+  for (int i = 0; i < nMags->n; i++) {
+    if (nMags->data.F32[i] < 1) continue;
+    if ((i > iPeak) && (nMags->data.F32[i] < 0.8*nPeak)) continue;
+    lnMag->data.F32[n] = log10 (nMags->data.F32[i]);
+    Mag->data.F32[n] = mMin + (i + 0.5)*dMag;
+    n++;
+  }
+  lnMag->n = n;
+  Mag->n = n;
+  psLogMsg ("psastro", 4, "fitting %d points to luminosity function\n", n);
+
+  psVector *mask = psVectorAlloc (Mag->n, PS_TYPE_MASK);
+  psVectorInit (mask, 0);
+
+  psPolynomial1D *line = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
+  psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+  stats->clipSigma = 3.0;
+  stats->clipIter = 3;
+
+  if (!psVectorClipFitPolynomial1D(line, stats, mask, 0xff, lnMag, NULL, Mag)) {
+      psError(PS_ERR_UNKNOWN, false, "Failed the fit the luminosity function\n");
+      return(NULL);
+  }
+
+  // find min and max unmasked magnitudes
+  double mMinValid = NAN;
+  double mMaxValid = NAN;
+  for (int i = 0; i < Mag->n; i++) {
+    if (mask->data.U8[i]) continue;
+    if (isnan(mMinValid) || (Mag->data.F32[i] < mMinValid)) {
+      mMinValid = Mag->data.F32[i];
+    }
+    if (isnan(mMaxValid) || (Mag->data.F32[i] > mMaxValid)) {
+      mMaxValid = Mag->data.F32[i];
+    }
+  }
+
+  psLogMsg ("psastro", 4, "logN = %f + %f mag\n", line->coeff[0], line->coeff[1]);
+  psLogMsg ("psastro", 4, "logN vs mag residuals: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+  pmLumFunc *lumFunc = pmLumFuncAlloc (mMinValid, mMaxValid, line->coeff[0], line->coeff[1]);
+
+  psFree (lnMag);
+  psFree (nMags);
+  psFree (Mag);
+  psFree (mask);
+  psFree (line);
+  psFree (stats);
+
+  return (lumFunc);
+}
+
+
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicAstrom.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicAstrom.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicAstrom.c	(revision 21632)
@@ -0,0 +1,86 @@
+# include "psastro.h"
+# define NONLIN_TOL 0.001 /* tolerance in pixels */
+
+// XXX require this fpa to have multiple chip extensions and a PHU?
+bool psastroMosaicAstrom (pmConfig *config, psArray *refs) {
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+	return false;
+    }
+
+    pmFPA *fpa = input->fpa;
+
+    // XXX before we do object matches, we need to fix failed chips
+    // compare chips with supplied mosaic model
+    // adjust significant outliers to match model
+
+    // given the existing per-chip astrometry, determine matches between raw and ref stars
+    // is this needed? yes, if we didn't do SingleChip astrometry first
+    psastroMosaicSetMatch (fpa, recipe, 0);
+    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.0.dat"); }
+
+    // fitted chips will follow the local plate-scale, hiding the distortion
+    // modify the chip->toFPA scaling to match knowledge about pixel scale,
+    // then recalculate raw and ref positions
+    psastroMosaicCommonScale (fpa, recipe);
+    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.1.dat"); }
+
+    // fit the distortion by fitting its gradient
+    // apply the new distortion terms up and down
+    // refit the per-chip terms with linear fits only
+    psastroMosaicGradients (fpa, recipe);
+    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.2.dat"); }
+
+    psastroMosaicChipAstrom (fpa, recipe, false);
+    if (psTraceGetLevel("psastro.dump") > 0) { psastroDumpMatches (fpa, "match.3.dat"); }
+
+    // re-perform the match with a slightly tighter circle
+    psastroMosaicSetMatch (fpa, recipe, 1);
+
+    // do a second pass on the distortion with improved chip positions and rotations
+    psastroMosaicCommonScale (fpa, recipe);
+    psastroMosaicGradients (fpa, recipe);
+    psastroMosaicChipAstrom (fpa, recipe, false);
+    psastroMosaicSetMatch (fpa, recipe, 1);
+    
+    // now fit the chips under the common distortion with higher-order terms
+    // first, re-perform the match with a slightly tighter circle
+    psastroMosaicChipAstrom (fpa, recipe, true);
+
+    // save WCS and analysis metadata in update header
+    // XXX also need to add DATE/TIME info and NAXIS1, NAXIS2
+    psMetadata *updates = psMetadataAlloc();
+    psMetadataAddS32 (updates, PS_LIST_TAIL, "NAXIS1",   PS_META_REPLACE, "fpa dimensions (um)", 30000);
+    psMetadataAddS32 (updates, PS_LIST_TAIL, "NAXIS2",   PS_META_REPLACE, "fpa dimensions (um)", 30000);
+    psMetadataAddStr (updates, PS_LIST_TAIL, "DATE-OBS", PS_META_REPLACE, "date", "2003-08-28");
+    psMetadataAddStr (updates, PS_LIST_TAIL, "UTC-OBS",  PS_META_REPLACE, "date", "7:15:46.47");
+    
+    pmAstromWriteBilevelMosaic (updates, fpa, NONLIN_TOL);
+    psMetadataAddMetadata (fpa->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+    psFree (updates);
+
+    // update the headers based on the results
+    // XXX need to add global summary statistics
+    // psastroMosaicHeaders (config);
+
+    return true;
+}
+
+/* coordinate frame hierachy
+ * pixels (on a given readout)
+ * cell
+ * chip
+ * FP (focal plane)
+ * TP (tangent plane)
+ * sky (ra, dec)
+ */
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicChipAstrom.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicChipAstrom.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicChipAstrom.c	(revision 21632)
@@ -0,0 +1,39 @@
+# include "psastro.h"
+# define NONLIN_TOL 0.001 /* tolerance in pixels */
+
+bool psastroMosaicChipAstrom (pmFPA *fpa, psMetadata *recipe, bool nonlinear) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// save WCS and analysis metadata in update header
+		psMetadata *updates = psMetadataAlloc();
+
+		psastroMosaicOneChip (chip, readout, recipe, updates, nonlinear);
+
+		// create the header keywords to descripe the results
+		pmAstromWriteBilevelChip (updates, chip, NONLIN_TOL);
+		psMetadataAddMetadata (readout->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+		psFree (updates);
+	    }
+	}
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicGetGrads.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicGetGrads.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicGetGrads.c	(revision 21632)
@@ -0,0 +1,44 @@
+# include "psastro.h"
+
+psArray *psastroMosaicGetGrads (pmFPA *fpa, psMetadata *recipe) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    psArray *grads = NULL;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		psArray *match = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+		if (match == NULL) { continue; }
+
+		// measure the local gradients for this set of stars
+		// XXX update the function prototype to accept an incoming gradient structure to which the new elements are added
+		grads = pmAstromMeasureGradients (grads, rawstars, refstars, match, recipe);
+	    }
+	}
+    }
+    return (grads);
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicGradients.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicGradients.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicGradients.c	(revision 21632)
@@ -0,0 +1,68 @@
+# include "psastro.h"
+
+bool psastroMosaicGradients (pmFPA *fpa, psMetadata *recipe) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    psArray *gradients = NULL;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		psArray *match = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+		if (match == NULL) { continue; }
+
+		// measure the local gradients for this set of stars
+		gradients = pmAstromMeasureGradients (gradients, rawstars, refstars, match, recipe);
+	    }
+	}
+    }
+
+    // allocate mosaic-level polynomial transformation and set masks needed by DVO
+    int order = psMetadataLookupF32 (&status, recipe, "PSASTRO.MOSAIC.ORDER");
+    if (!status) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to find single-chip fit order\n");
+        return false;
+    }
+    psFree (fpa->toTPA);
+    fpa->toTPA = psPlaneTransformAlloc (order, order);
+    for (int i = 0; i <= fpa->toTPA->x->nX; i++) {
+        for (int j = 0; j <= fpa->toTPA->x->nY; j++) {
+            if (i + j > order) {
+		fpa->toTPA->x->mask[i][j] = 1;
+		fpa->toTPA->y->mask[i][j] = 1;
+            }
+        }
+    }
+
+    // fit the measured gradients with the telescope distortion model (polynomial order based on toTPA)
+    pmAstromFitDistortion (fpa, gradients, recipe);
+    psastroMosaicSetAstrom (fpa);
+
+    psFree (gradients);
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicHeaders.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicHeaders.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicHeaders.c	(revision 21632)
@@ -0,0 +1,59 @@
+# include "psastro.h"
+
+bool psastroMosaicHeaders (pmConfig *config) {
+
+    bool status = false;
+    pmChip *chip = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+ 	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+	return false;
+    }
+
+    char *mosastro = psMetadataLookupStr (NULL, config->arguments, "MOSASTRO");
+
+    double plateScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLATE.SCALE");
+    if (!status) plateScale = 1.0;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPA *fpa = input->fpa;
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
+
+        pmAstromWriteBilevelChip (chip->toFPA, hdu->header, plateScale);
+    }
+
+    psMetadata *mosaic = pmAstromWriteBilevelMosaic (fpa->toSky, fpa->toTPA, plateScale);
+
+    // XXX what is the EXTNAME??
+    psFits *fits = psFitsOpen (mosastro, "w");
+    psFitsWriteBlank(fits, mosaic, "");
+    psFitsClose (fits);
+
+    psFree (view);
+    return true;
+}
+
+/* coordinate frame hierachy
+   pixels (on a given readout)
+   cell
+   chip
+   FP (focal plane)
+   TP (tangent plane)
+   sky (ra, dec)
+*/
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicOneChip.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicOneChip.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicOneChip.c	(revision 21632)
@@ -0,0 +1,117 @@
+# include "psastro.h"
+
+# define REQUIRED_RECIPE_VALUE(VALUE, NAME, TYPE, MESSAGE)\
+  VALUE = psMetadataLookup##TYPE (&status, recipe, NAME); \
+  if (!status) { \
+   psError(PSASTRO_ERR_CONFIG, false, MESSAGE); \
+   return false; } 
+
+bool psastroMosaicOneChip (pmChip *chip, pmReadout *readout, psMetadata *recipe, psMetadata *updates, bool nonlinear) {
+
+    bool status;
+
+    PS_ASSERT_PTR_NON_NULL(chip,    false);
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe,  false);
+    PS_ASSERT_PTR_NON_NULL(updates, false);
+
+    // select the raw objects for this readout
+    psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+    if (rawstars == NULL) return false;
+
+    psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+    if (refstars == NULL) return false;
+
+    psArray *match = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+    if (match == NULL) return false;
+
+    // correct radius to FP units (physical pixel scale in microns per pixel)
+    REQUIRED_RECIPE_VALUE (double pixelScale, "PSASTRO.PIXEL.SCALE", F32, "Failed to lookup pixel scale"); 
+
+    // allowed limits for valid solutions
+    REQUIRED_RECIPE_VALUE (float maxError, "PSASTRO.MOSAIC.MAX.ERROR", F32, "failed to find single-chip max allowed error\n");
+    REQUIRED_RECIPE_VALUE (int minNstar, "PSASTRO.MOSAIC.MIN.NSTAR", S32, "failed to find single-chip min allowed stars\n");
+
+    // set the order of the per-chip fit (higher order only if nonlinear == true)
+    int order = 1;
+    if (nonlinear) {
+	// select the desired chip order
+	REQUIRED_RECIPE_VALUE (order, "PSASTRO.MOSAIC.CHIP.ORDER", S32, "failed to find mosaic chip-level fit order\n");
+
+	// modify the order to correspond to the actual number of matched stars:
+	if ((match->n < 11) && (order >= 3)) order = 2;
+	if ((match->n <  7) && (order >= 2)) order = 1;
+	if ((match->n <  4) && (order >= 1)) order = 0;
+	if (order < 1) {
+	    psLogMsg ("psastro", 3, "insufficient stars or invalid order: %ld stars", match->n); 
+	    return false; 
+	} 
+    }
+
+    // create output toFPA; set masks appropriate to the Elixir DVO astrometry format
+    psFree (chip->toFPA);
+    chip->toFPA = psPlaneTransformAlloc (order, order);
+    for (int i = 0; i <= chip->toFPA->x->nX; i++) {
+        for (int j = 0; j <= chip->toFPA->x->nY; j++) {
+            if (i + j > order) {
+		chip->toFPA->x->mask[i][j] = 1;
+		chip->toFPA->y->mask[i][j] = 1;
+            }
+        }
+    }
+
+    // XXX allow statitic to be set by the user
+    psStats *fitStats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+    fitStats->clipSigma = psMetadataLookupF32 (&status, recipe, "PSASTRO.MOSAIC.CHIP.NSIGMA");
+    fitStats->clipIter = psMetadataLookupS32 (&status, recipe, "PSASTRO.MOSAIC.CHIP.NITER");
+
+    // need to pass in an update header, sent in from above
+    pmAstromFitResults *results = pmAstromMatchFit (chip->toFPA, rawstars, refstars, match, fitStats);
+    if (!results) {
+	psError(PSASTRO_ERR_DATA, false, "failed to perform the matched fit\n");
+	return false;
+    }
+
+    // toSky converts from FPA & TPA units (microns) to sky units (radians)
+    pmFPA *fpa = chip->parent;
+    float plateScale = 0.5*(fpa->toSky->Xs + fpa->toSky->Ys)*3600.0*PM_DEG_RAD;
+
+    // pixError is the average 1D scatter in pixels ('results' are in FPA units = microns)
+    float pixError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) / pixelScale;
+
+    // astError is the average 1D scatter in arcsec ('results' are in FPA units = microns)
+    float astError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) * plateScale;
+    int astNstar = results->yStats->clippedNvalues;
+
+    bool validSolution = true;
+
+    // XXX should these result in errors or be handled another way?
+    psLogMsg ("psastro", PS_LOG_INFO, "astrometry solution: error: %f arcsec, Nstars: %d", astError, astNstar);
+    if (astError > maxError) {
+	psLogMsg("psastro", PS_LOG_INFO, "residual error is too large, failed to find a solution: %f > %f", astError, maxError);
+	validSolution = false;
+    }
+    if (astNstar < minNstar) {
+	psLogMsg("psastro", PS_LOG_INFO, "solution uses too few stars: %d < %d", astNstar, minNstar);
+	validSolution = false;
+    }
+
+    // DVO expects NASTRO = 0 if we fail to find a solution
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "PERROR",   PS_META_REPLACE, "astrometry error (pixels)", pixError);
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "CERROR",   PS_META_REPLACE, "astrometry error (arcsec)", astError);
+    if (validSolution) {
+	psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", astError/sqrt(astNstar));
+	psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", astNstar);
+    } else {
+	psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", 0.0);
+	psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", 0);
+    }
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "EQUINOX",  PS_META_REPLACE, "", 2000.0); // XXX this is bogus: should be defined based on equinox of refstars
+
+    // determine fromFPA transformation and apply new transformation to raw & ref stars
+    psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+
+    psFree (fitStats);
+    psFree (results);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicRescaleChips.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicRescaleChips.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicRescaleChips.c	(revision 21632)
@@ -0,0 +1,26 @@
+# include "psastro.h"
+
+// XXX what is this doing? 
+// shouldn't it be doing toFPA -> fromFPA
+
+bool psastroMosaicRescaleChips (pmFPA *fpa) {
+
+    pmChip *chip = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+
+    // psRegion region = psMetadataLookupXXX (chip->concepts, "CHIP.TRIMSEC"); 
+    // psRegion region = psRegionSet (0, 4000, 0, 4000);
+    // chip->fromFPA = psPlaneTransformInvert(NULL, toFPA, region, 50);
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+	// XXX make '50' be a fraction of the chip size?
+	chip->fromFPA = psPlaneTransformInvert(chip->fromFPA, chip->toFPA, region, 50);
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicSetAstrom.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicSetAstrom.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicSetAstrom.c	(revision 21632)
@@ -0,0 +1,51 @@
+# include "psastro.h"
+
+bool psastroMosaicSetAstrom (pmFPA *fpa) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		for (int i = 0; i < rawstars->n; i++) {
+		    pmAstromObj *raw = rawstars->data[i];
+	
+		    psPlaneTransformApply (raw->FP, chip->toFPA, raw->chip);
+		    psPlaneTransformApply (raw->TP, fpa->toTPA, raw->FP);
+		    psDeproject (raw->sky, raw->TP, fpa->toSky);
+		}
+
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		for (int i = 0; i < refstars->n; i++) {
+		    pmAstromObj *ref = refstars->data[i];
+	
+		    psProject (ref->TP, ref->sky, fpa->toSky);
+		    psPlaneTransformApply (ref->FP, fpa->fromTPA, ref->TP);
+		    psPlaneTransformApply (ref->chip, chip->fromFPA, ref->FP);
+		}
+	    }
+	}
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicSetMatch.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicSetMatch.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroMosaicSetMatch.c	(revision 21632)
@@ -0,0 +1,54 @@
+# include "psastro.h"
+
+bool psastroMosaicSetMatch (pmFPA *fpa, psMetadata *recipe, int iteration) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+    char radiusWord[64];
+
+    // use small radius to match stars (assume starting astrometry is good)
+    bool status = false; 
+    sprintf (radiusWord, "PSASTRO.MOSAIC.RADIUS.N%d", iteration);
+    double RADIUS = psMetadataLookupF32 (&status, recipe, radiusWord); 
+    if (!status) { 
+	psError(PS_ERR_IO, false, "Failed to lookup matching radius: %s", radiusWord); 
+	return NULL; 
+    } 
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+		psTrace ("psastro", 4, "Trying %ld refstars\n", refstars->n);
+
+		psArray *matches = pmAstromRadiusMatchChip (rawstars, refstars, RADIUS);
+		psTrace ("psastro", 4, "Matched %ld refstars\n", matches->n);
+
+		// XXX drop the old one
+		psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.MATCH", PS_DATA_ARRAY | PS_META_REPLACE, "astrometry matches", matches);
+		psFree (matches);
+	    }
+	}
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroOneChip.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroOneChip.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroOneChip.c	(revision 21632)
@@ -0,0 +1,146 @@
+# include "psastro.h"
+
+# define REQUIRED_RECIPE_VALUE(VALUE, NAME, TYPE, MESSAGE)\
+  VALUE = psMetadataLookup##TYPE (&status, recipe, NAME); \
+  if (!status) { \
+   psError(PSASTRO_ERR_CONFIG, false, MESSAGE); \
+   return false; } 
+
+bool psastroOneChip (pmFPA *fpa, pmChip *chip, psArray *refstars, psArray *rawstars, psMetadata *recipe, psMetadata *updates) {
+
+    bool status;
+
+    // supplied radius is in pixels
+    REQUIRED_RECIPE_VALUE (double RADIUS, "PSASTRO.MATCH.RADIUS", F32, "Failed to lookup matching radius"); 
+
+    // correct radius to FP units (physical pixel scale in microns per pixel)
+    REQUIRED_RECIPE_VALUE (double pixelScale, "PSASTRO.PIXEL.SCALE", F32, "Failed to lookup pixel scale"); 
+    RADIUS *= pixelScale;
+
+    // select the desired chip order
+    REQUIRED_RECIPE_VALUE (int order, "PSASTRO.CHIP.ORDER", S32, "failed to find single-chip fit order\n");
+
+    // allowed limits for valid solutions
+    REQUIRED_RECIPE_VALUE (float maxError, "PSASTRO.MAX.ERROR", F32, "failed to find single-chip max allowed error\n");
+    REQUIRED_RECIPE_VALUE (int minNstar, "PSASTRO.MIN.NSTAR", S32, "failed to find single-chip min allowed stars\n");
+
+    // find initial offset / rotation
+    pmAstromStats *gridStats = pmAstromGridMatch (rawstars, refstars, recipe);
+    if (gridStats == NULL) {
+	psLogMsg ("psastro", 3, "failed to find a grid match solution\n");
+        return false;
+    }
+    psLogMsg ("psastro", 3, "basic grid search result - offset: %f,%f pixels, rotation: %f deg\n", gridStats->offset.x, gridStats->offset.y, DEG_RAD*gridStats->angle);
+
+    // tweak the position by finding peak of matches stars
+    pmAstromStats *stats = pmAstromGridTweak (rawstars, refstars, recipe, gridStats);
+    if (stats == NULL) {
+	psLogMsg ("psastro", 3, "failed to measure tweaked grid solution\n");
+        return false;
+    }
+    psLogMsg ("psastro", 3, "tweak grid search result - offset: %f,%f pixels, rotation: %f deg\n", stats->offset.x, stats->offset.y, DEG_RAD*stats->angle);
+
+    // adjust the chip.toFPA terms only
+    pmAstromGridApply (chip->toFPA, stats);
+    psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+
+    // use small radius to match stars
+    psArray *match = pmAstromRadiusMatchFP (rawstars, refstars, RADIUS);
+    if (match == NULL) {
+	psLogMsg ("psastro", 3, "failed to find radius-matched sources\n");
+        return false;
+    }
+
+    // modify the order to correspond to the actual number of matched stars:
+    if ((match->n < 11) && (order >= 3)) order = 2;
+    if ((match->n <  7) && (order >= 2)) order = 1;
+    if ((match->n <  4) && (order >= 1)) order = 0;
+    if (order < 1) {
+	psLogMsg ("psastro", 3, "insufficient stars or invalid order: %ld stars", match->n); 
+	psFree (match);
+	psFree (stats);
+	psFree (gridStats);
+	return false; 
+    } 
+
+    // create output toFPA; set masks appropriate to the Elixir DVO astrometry format
+    psFree (chip->toFPA);
+    chip->toFPA = psPlaneTransformAlloc (order, order);
+    for (int i = 0; i <= chip->toFPA->x->nX; i++) {
+        for (int j = 0; j <= chip->toFPA->x->nY; j++) {
+            if (i + j > order) {
+		chip->toFPA->x->mask[i][j] = 1;
+		chip->toFPA->y->mask[i][j] = 1;
+            }
+        }
+    }
+
+    // XXX allow statitic to be set by the user
+    psStats *fitStats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+    fitStats->clipSigma = psMetadataLookupF32 (&status, recipe, "PSASTRO.CHIP.NSIGMA");
+    fitStats->clipIter = psMetadataLookupS32 (&status, recipe, "PSASTRO.CHIP.NITER");
+
+    // improved fit for astrometric terms
+    pmAstromFitResults *results = pmAstromMatchFit (chip->toFPA, rawstars, refstars, match, fitStats);
+    if (!results) {
+	psLogMsg ("psastro", 3, "failed to perform the matched fit\n");
+        return false;
+    }
+    
+    // toSky converts from FPA & TPA units (microns) to sky units (radians)
+    float plateScale = 0.5*(fpa->toSky->Xs + fpa->toSky->Ys)*3600.0*PM_DEG_RAD;
+
+    // pixError is the average 1D scatter in pixels ('results' are in FPA units = microns)
+    float pixError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) / pixelScale;
+
+    // astError is the average 1D scatter in arcsec ('results' are in FPA units = microns)
+    float astError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) * plateScale;
+    int astNstar = results->yStats->clippedNvalues;
+
+    bool validSolution = true;
+
+    // XXX should these result in errors or be handled another way?
+    psLogMsg ("psastro", PS_LOG_INFO, "astrometry solution: error: %f arcsec, Nstars: %d", astError, astNstar);
+    if (astError > maxError) {
+        psLogMsg("psastro", PS_LOG_INFO, "residual error is too large, failed to find a solution: %f > %f", astError, maxError);
+	validSolution = false;
+    }
+    if (astNstar < minNstar) {
+        psLogMsg("psastro", PS_LOG_INFO, "solution uses too few stars: %d < %d", astNstar, minNstar);
+	validSolution = false;
+    }
+
+    // DVO expects NASTRO = 0 if we fail to find a solution
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "PERROR",   PS_META_REPLACE, "astrometry error (pixels)", pixError);
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "CERROR",   PS_META_REPLACE, "astrometry error (arcsec)", astError);
+    if (validSolution) {
+	psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", astError/sqrt(astNstar));
+	psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", astNstar);
+    } else {
+	psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", 0.0);
+	psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", 0);
+    }
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "EQUINOX",  PS_META_REPLACE, "equinox of ref catalog", 2000.0); // XXX this is bogus: should be defined based on equinox of refstars
+
+    // determine fromFPA transformation and apply new transformation to raw & ref stars
+    psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+    
+    // XXX check if we correctly applied the new transformation:
+    psastroDumpRawstars (rawstars, fpa, chip);
+
+    if (psTraceGetLevel("psastro.plot") > 0) {
+	psastroPlotOneChipFit (rawstars, refstars, match, results);
+    }
+
+    psFree (match);
+    psFree (stats);
+    psFree (results);
+    psFree (fitStats);
+    psFree (gridStats);
+    return true;
+}
+
+// psastroWriteStars ("raw.1.dat", rawstars);
+// psastroWriteStars ("ref.1.dat", refstars);
+// psastroWriteTransform (chip->toFPA);
+
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroParseCamera.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroParseCamera.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroParseCamera.c	(revision 21632)
@@ -0,0 +1,43 @@
+# include "psastro.h"
+
+bool psastroParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the input image(s) are required arguments; they define the camera
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PSASTRO.INPUT", "INPUT");
+    if (!status) {
+	psError(PSASTRO_ERR_CONFIG, false, "Failed to build FPA from PSASTRO.INPUT");
+	return false;
+    }
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+
+    // set default recipe values here
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE",    PS_META_NO_REPLACE, "", "NONE");
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "BREAK_POINT", PS_META_NO_REPLACE, "", "NONE");
+
+    // these calls bind the I/O handle to the specified fpa
+    pmFPAfileDefineOutput (config, input->fpa, "PSASTRO.OUTPUT");
+
+    // Chip selection: turn on only the chips specified (option is not required)
+    char *chipLine = psMetadataLookupStr(NULL, config->arguments, "CHIP_SELECTIONS"); 
+    if (chipLine == NULL) psErrorClear();
+
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+		psError(PSASTRO_ERR_CONFIG, true, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+
+    psTrace("psastro", 1, "Done with psastroParseCamera...\n");
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroRefstarSubset.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroRefstarSubset.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroRefstarSubset.c	(revision 21632)
@@ -0,0 +1,74 @@
+# include "psastro.h"
+
+bool psastroRefstarSubset (pmReadout *readout) {
+
+  // select the raw objects for this readout
+  psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+  if (rawstars == NULL)  {
+    psError(PSASTRO_ERR_DATA, false, "missing rawstars in psastroRefstarSubset\n");
+    return false;
+  }
+
+  // select the raw objects for this readout
+  psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+  if (refstars == NULL)  {
+    psError(PSASTRO_ERR_DATA, false, "missing refstars in psastroRefstarSubset\n");
+    return false;
+  }
+
+  // calculate luminosity functions for rawstars and refstars
+  // the samples cover the same area (the chip), so no area correction
+  // is needed...
+  psLogMsg ("psastro", 4, "measuring luminosity function for rawstars\n");
+  pmLumFunc *rawfunc = psastroLuminosityFunction (rawstars);
+  if (rawfunc == NULL) {
+    psError(PSASTRO_ERR_DATA, false, "error measuring rawstar luminosity function\n");
+    return false;
+  }
+  psLogMsg ("psastro", 4, "measuring luminosity function for refstars\n");
+  pmLumFunc *reffunc = psastroLuminosityFunction (refstars);
+  if (reffunc == NULL) {
+    psError(PSASTRO_ERR_DATA, false, "error measuring refstar luminosity function\n");
+    return false;
+  }
+
+  // what is the offset between the two lines at the average magnitude?
+  double mRef = 0.5*(reffunc->mMin + reffunc->mMax);
+  double logRho = mRef * reffunc->slope + reffunc->offset;
+  double mRaw = (logRho - rawfunc->offset) / rawfunc->slope;
+
+  psLogMsg ("psastro", 4, "mRef: %f, logRho: %f, mRaw: %f\n", mRef, logRho, mRaw);
+
+  double mRefMax = rawfunc->mMax - mRaw + mRef;
+  psLogMsg ("psastro", 4, "clipping stars fainter than %f\n", mRefMax);
+
+  psArray *subset = psArrayAllocEmpty (100);
+  for (int i = 0; i < refstars->n; i++) {
+    pmAstromObj *ref = refstars->data[i];
+    if (ref->Mag > mRefMax) continue;
+    psArrayAdd (subset, 100, ref);
+  }
+
+  psLogMsg ("psastro", 4, "keeping %ld of %ld reference stars\n", subset->n, refstars->n);
+
+  psMetadataRemoveKey (readout->analysis, "PSASTRO.REFSTARS");
+  psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.REFSTARS", PS_DATA_ARRAY, "astrometry matches", subset);
+
+  if (psTraceGetLevel("psastro.dump") > 0) {
+      psastroDumpRefstars (subset, "refstars.subset.dat");
+  }
+
+  psFree (rawfunc);
+  psFree (reffunc);
+  psFree (subset);
+
+  return true;
+}
+
+/* this test is a bit sensitive to the total number of refstars or rawstars available
+   watch out if:
+   - the fitted slopes are extremely different 
+   - the average number of stars per bin is ~1
+   
+   skip the cut in these cases?
+*/
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroTestFuncs.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroTestFuncs.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroTestFuncs.c	(revision 21632)
@@ -0,0 +1,45 @@
+# include "psastro.h"
+
+// write out objects
+bool psastroWriteStars (char *filename, psArray *sources) {
+
+    // re-open, add data to end of file
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("psastroWriteStars", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+
+    for (int i = 0; i < sources->n; i++) {
+	
+	pmAstromObj *star = sources->data[i];
+
+	fprintf (f, "%8.2f %8.2f   %8.2f %8.2f   %8.2f %8.2f   %10.6f %10.6f   %8.2f %8.2f\n", 
+		 star->chip->x, star->chip->y, 
+		 star->FP->x, star->FP->y, 
+		 star->TP->x, star->TP->y, 
+		 star->sky->r*DEG_RAD, star->sky->d*DEG_RAD, 
+		 star->Mag, star->dMag);
+    }
+    fclose (f);
+    return true;
+}
+
+bool psastroWriteTransform (psPlaneTransform *map) {
+
+    // dump initial values:
+    for (int i = 0; i < map->x->nX + 1; i++) {
+	for (int j = 0; j < map->x->nY + 1; j++) {
+	    if (map->x->mask[i][j]) continue;
+	    psLogMsg ("psastro", 4, "x term %d,%d: %f +/- %f\n", i, j, map->x->coeff[i][j], map->x->coeffErr[i][j]);
+	}
+    }
+
+    for (int i = 0; i < map->y->nX + 1; i++) {
+	for (int j = 0; j < map->y->nY + 1; j++) {
+	    if (map->y->mask[i][j]) continue;
+	    psLogMsg ("psastro", 4, "y term %d,%d: %f +/- %f\n", i, j, map->y->coeff[i][j], map->y->coeffErr[i][j]);
+	}
+    }
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroUtils.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroUtils.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroUtils.c	(revision 21632)
@@ -0,0 +1,397 @@
+# include "psastro.h"
+# define RENORM 0
+
+// fix this to look up the value in the chip concepts
+static double getChipPixelScale (pmChip *chip) {
+    return 10.0;
+}
+
+// I have an FPA structure with multiple chips.  we have loaded or measured astrometry for each
+// chip with independent pixel/degree scaling values.  These will naturally compensate locally
+// somewhat for the telescope distortion.  to measure the telescope distortion, we need to
+// force the chips to have the same pixel scale and measure the difference from that solution.
+// Convert an FPA with disparate pixel scales to a common pixel scale (perhaps depending on the
+// chip -- eg, TC3)
+
+bool psastroMosaicCommonScale (pmFPA *fpa, psMetadata *recipe) {
+
+    // options : use the MIN or MAX chip as global reference or supplied pixel scales
+    // (microns/pixel), which may depend on the chip
+
+    float pixelScaleUse = 1.0, pixelScale1 = 1.0,  pixelScale2 = 1.0,  pixelScale = 1.0;
+
+    char *option = psMetadataLookupStr (NULL, recipe, "PSASTRO.COMMON.SCALE.OPTION");
+    if (option == NULL) {
+	psError(PSASTRO_ERR_DATA, false, "no choice set for common scale option\n");
+	return false;
+    }
+
+    bool useExternal = true;
+
+    // find the min or max scale chip
+    if (!strcasecmp (option, "MIN") || !strcasecmp (option, "MAX")) {
+
+	bool useMax = !strcasecmp (option, "MAX");
+	pixelScaleUse = (useMax) ? FLT_MIN : FLT_MAX;
+
+	for (int i = 0; i < fpa->chips->n; i++) {
+	    pmChip *chip = fpa->chips->data[i];
+	    if (!chip->process || !chip->file_exists) { continue; }
+	    
+	    pixelScale1 = hypot (chip->toFPA->x->coeff[1][0], chip->toFPA->x->coeff[0][1]);
+	    pixelScale2 = hypot (chip->toFPA->y->coeff[1][0], chip->toFPA->y->coeff[0][1]);
+	    pixelScale = 0.5*(pixelScale1 + pixelScale2);
+	    
+	    pixelScaleUse = (useMax) ? PS_MAX (pixelScale, pixelScaleUse) : PS_MIN (pixelScale, pixelScaleUse);
+	}
+	useExternal = false;
+    }
+
+    // rescale each chip by the reference scale
+    for (int i = 0; i < fpa->chips->n; i++) {
+	pmChip *chip = fpa->chips->data[i];
+	if (!chip->process || !chip->file_exists) { continue; }
+
+	psPlaneTransform *toFPA = chip->toFPA;
+	psPlaneTransform *fromFPA = chip->fromFPA;
+	    
+	pixelScale1 = hypot (toFPA->x->coeff[1][0], toFPA->x->coeff[0][1]);
+	pixelScale2 = hypot (toFPA->y->coeff[1][0], toFPA->y->coeff[0][1]);
+
+	if (useExternal) {
+	    pixelScaleUse = getChipPixelScale (chip);
+	}
+
+	for (int i = 0; i <= toFPA->x->nX; i++) {
+	    for (int j = 0; j <= toFPA->x->nX; j++) {
+		toFPA->x->coeff[i][j] *= pixelScaleUse/pixelScale1;
+		toFPA->y->coeff[i][j] *= pixelScaleUse/pixelScale2;
+		fromFPA->x->coeff[i][j] *= pixelScale1/pixelScaleUse;
+		fromFPA->y->coeff[i][j] *= pixelScale2/pixelScaleUse;
+	    }
+	}
+
+    }
+    psastroMosaicSetAstrom (fpa);
+    return true;
+}
+
+bool psastroUpdateChipToFPA (pmFPA *fpa, pmChip *chip, psArray *rawstars, psArray *refstars) {
+
+    // XXX this region needs to be defined more intelligently
+    // psRegion *region = pmChipExtent (chip);
+    psRegion *region = psRegionAlloc (0, 2000, 0, 2000);
+
+    psFree (chip->fromFPA);
+    chip->fromFPA = psPlaneTransformInvert (NULL, chip->toFPA, *region, 20);
+
+//    PS_POLY_PRINT_2D(chip->toFPA->x);
+//    PS_POLY_PRINT_2D(chip->toFPA->y);
+
+//    PS_POLY_PRINT_2D(chip->fromFPA->x);
+//    PS_POLY_PRINT_2D(chip->fromFPA->y);
+
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+
+        psPlaneTransformApply (raw->FP, chip->toFPA, raw->chip);
+        psPlaneTransformApply (raw->TP, fpa->toTPA, raw->FP);
+        psDeproject (raw->sky, raw->TP, fpa->toSky);
+    }
+
+    for (int i = 0; i < refstars->n; i++) {
+        pmAstromObj *ref = refstars->data[i];
+        psPlaneTransformApply (ref->chip, chip->fromFPA, ref->FP);
+    }
+
+    psFree (region);
+    return true;
+}
+
+# if 0
+
+bool psastroSelectBrightStars (pmFPA *fpa, psMetadata *config) {
+
+    bool status;
+
+    // add exclusions for objects on some basis?
+    int MAX_NSTARS = psMetadataLookupS32 (&status, config, "PSASTRO.STARS.MAX");
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j];
+            for (int k = 0; k < cell->readouts->n; k++) {
+                pmReadout *readout = cell->readouts->data[k];
+
+                psArray *stars = psMetadataLookupPtr (&status, readout->analysis, "STARS.FULLSET");
+                stars = psArraySort (stars, pmAstromObjSortByMag);
+
+                int nSubset = PS_MIN (MAX_NSTARS, stars->n);
+                psArray *subset = psArrayAlloc (nSubset);
+
+                for (int i = 0; i < nSubset; i++) {
+                    subset->data[i] = stars->data[i];
+                }
+                psMetadataAdd (readout->analysis, PS_LIST_TAIL, "STARS.SUBSET", PS_DATA_ARRAY, "stars from analysis", subset);
+            }
+        }
+    }
+    return true;
+}
+
+bool psastroNormFPA (pmFPA *fpa, psMetadata *config) {
+
+    // save the raw astrometry for later reference
+    pmFPA *raw = pmFPACopyAstrom (fpa);
+
+    // first pass: measure the per-chip solutions, modify the chip.toFPA terms
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        psastroChipAstrom (chip, config);
+    }
+
+    // second stage: re-normalize the chip terms, passing the
+    // average rotation and offset values to the fpa.toSky
+    if (RENORM) {
+
+        // this code is needed for the mosastro stage, with multiple chip solutions
+
+        double dX, dY, dT, dN;
+        dX = dY = dT = dN = 0;
+
+        psPlane origin, P1, P2;
+        origin.x = 0;
+        origin.y = 0;
+
+        // calculate the average rotation and boresite offset relative to raw
+        for (int i = 0; i < fpa->chips->n; i++) {
+            pmChip *iChip = raw->chips->data[i];
+            pmChip *oChip = fpa->chips->data[i];
+
+            // offset of chip
+            psCoordChipToFPA (&P1, &origin, iChip);
+            psCoordChipToFPA (&P2, &origin, oChip);
+            dX += (P2.x - P1.x);
+            dY += (P2.y - P1.y);
+
+            // get parity-independent rotations for old and new solutions
+            double T1 = psPlaneTransformGetRotation (iChip->toFPA);
+            double T2 = psPlaneTransformGetRotation (oChip->toFPA);
+            dT += T2 - T1;
+            dN ++;
+        }
+
+        dT /= dN;
+        dX /= dN;
+        dY /= dN;
+
+        // R(T)
+        double PC1_1 = fpa->toTPA->x->coeff[1][0][0][0];
+        double PC1_2 = fpa->toTPA->x->coeff[0][1][0][0];
+        double PC2_1 = fpa->toTPA->y->coeff[1][0][0][0];
+        double PC2_2 = fpa->toTPA->y->coeff[0][1][0][0];
+
+        // R(dT)
+        double dPC1_1 = +cos (dT);
+        double dPC1_2 = +sin (dT);
+        double dPC2_1 = -sin (dT);
+        double dPC2_2 = +cos (dT);
+
+        // R'(T) = R(T) * R(dT)
+        double pc1_1 = PC1_1*dPC1_1 + PC1_2*dPC2_1;
+        double pc1_2 = PC1_1*dPC1_2 + PC1_2*dPC2_2;
+        double pc2_1 = PC2_1*dPC1_1 + PC2_2*dPC2_1;
+        double pc2_2 = PC2_1*dPC1_2 + PC2_2*dPC2_2;
+
+        double det = 1.0 / (pc1_1*pc2_2 - pc1_2*pc2_1);
+
+        // R'(-T)  (matrix inverse, not just rotation inverse -- keeps parity)
+        double pi1_1 = +pc2_2 * det;
+        double pi1_2 = -pc1_2 * det;
+        double pi2_1 = -pc2_1 * det;
+        double pi2_2 = +pc1_1 * det;
+
+        // apply the new modifcations in rotation and boresite
+        for (int i = 0; i < fpa->chips->n; i++) {
+            pmChip *oChip = fpa->chips->data[i];
+
+            // r(T)
+            double pr1_1 = oChip->toFPA->x->coeff[1][0];
+            double pr1_2 = oChip->toFPA->x->coeff[0][1];
+            double pr2_1 = oChip->toFPA->y->coeff[1][0];
+            double pr2_2 = oChip->toFPA->y->coeff[0][1];
+
+            // ri'(T) = R(T) r(t)
+            double ri1_1 = PC1_1*pr1_1 + PC1_2*pr2_1;
+            double ri1_2 = PC1_1*pr1_2 + PC1_2*pr2_2;
+            double ri2_1 = PC2_1*pr1_1 + PC2_2*pr2_1;
+            double ri2_2 = PC2_1*pr1_2 + PC2_2*pr2_2;
+
+            // r'(T) = R'(-T) ri'(T)
+            oChip->toFPA->x->coeff[1][0] = pi1_1*ri1_1 + pi1_2*ri2_1;
+            oChip->toFPA->x->coeff[0][1] = pi1_1*ri1_2 + pi1_2*ri2_2;
+            oChip->toFPA->y->coeff[1][0] = pi2_1*ri1_1 + pi2_2*ri2_1;
+            oChip->toFPA->y->coeff[0][1] = pi2_1*ri1_2 + pi2_2*ri2_2;
+
+            double dx = PC1_1*oChip->toFPA->x->coeff[0][0] + PC1_2*oChip->toFPA->y->coeff[0][0] + dX;
+            double dy = PC2_1*oChip->toFPA->x->coeff[0][0] + PC2_2*oChip->toFPA->y->coeff[0][0] + dY;
+
+            oChip->toFPA->x->coeff[0][0] = pi1_1*dx + pi1_2*dy;
+            oChip->toFPA->y->coeff[0][0] = pi2_1*dx + pi2_2*dy;
+        }
+
+        fpa->toTPA->x->coeff[0][0][0][0] -= dX;
+        fpa->toTPA->y->coeff[0][0][0][0] -= dY;
+
+        fpa->toTPA->x->coeff[1][0][0][0] = pc1_1;
+        fpa->toTPA->x->coeff[0][1][0][0] = pc1_2;
+        fpa->toTPA->y->coeff[1][0][0][0] = pc2_1;
+        fpa->toTPA->y->coeff[0][1][0][0] = pc2_2;
+    }
+    return true;
+}
+
+psPolynomial2D *psPolynomial2DCopy (psPolynomial2D *input) {
+
+    psPolynomial2D *output = psPolynomial2DAlloc (input->nX, input->nY, input->type);
+
+    for (int i = 0; i < input->nX; i++) {
+        for (int j = 0; j < input->nY; j++) {
+            output->mask[i][j]     = input->mask[i][j];
+            output->coeff[i][j]    = input->coeff[i][j];
+            output->coeffErr[i][j] = input->coeffErr[i][j];
+        }
+    }
+    return (output);
+}
+
+psPolynomial4D *psPolynomial4DCopy (psPolynomial4D *input) {
+
+    psPolynomial4D *output = psPolynomial4DAlloc (input->nX, input->nY, input->nZ, input->nT, input->type);
+
+    for (int i = 0; i < input->nX; i++) {
+        for (int j = 0; j < input->nY; j++) {
+            for (int k = 0; k < input->nZ; k++) {
+                for (int m = 0; m < input->nT; m++) {
+                    output->mask[i][j][k][m]     = input->mask[i][j][k][m];
+                    output->coeff[i][j][k][m]    = input->coeff[i][j][k][m];
+                    output->coeffErr[i][j][k][m] = input->coeffErr[i][j][k][m];
+                }
+            }
+        }
+    }
+    return (output);
+}
+
+psPlaneDistort *psPlaneDistortCopy (psPlaneDistort *input) {
+
+    psPlaneDistort *output = psPlaneDistortAlloc (input->x->nX, input->x->nY, input->x->nZ, input->x->nT);
+
+    for (int i = 0; i < input->x->nX; i++) {
+        for (int j = 0; j < input->x->nY; j++) {
+            for (int k = 0; k < input->x->nZ; k++) {
+                for (int m = 0; m < input->x->nT; m++) {
+                    // x-terms
+                    output->x->mask[i][j][k][m]     = input->x->mask[i][j][k][m];
+                    output->x->coeff[i][j][k][m]    = input->x->coeff[i][j][k][m];
+                    output->x->coeffErr[i][j][k][m] = input->x->coeffErr[i][j][k][m];
+                    // y-terms
+                    output->y->mask[i][j][k][m]     = input->y->mask[i][j][k][m];
+                    output->y->coeff[i][j][k][m]    = input->y->coeff[i][j][k][m];
+                    output->y->coeffErr[i][j][k][m] = input->y->coeffErr[i][j][k][m];
+                }
+            }
+        }
+    }
+    return (output);
+}
+
+psPlaneTransform *psPlaneTransformCopy (psPlaneTransform *input) {
+
+    psPlaneTransform *output = psPlaneTransformAlloc (input->x->nX, input->x->nY);
+
+    for (int i = 0; i < input->x->nX; i++) {
+        for (int j = 0; j < input->x->nY; j++) {
+            // x-terms
+            output->x->mask[i][j]     = input->x->mask[i][j];
+            output->x->coeff[i][j]    = input->x->coeff[i][j];
+            output->x->coeffErr[i][j] = input->x->coeffErr[i][j];
+            // y-terms
+            output->y->mask[i][j]     = input->y->mask[i][j];
+            output->y->coeff[i][j]    = input->y->coeff[i][j];
+            output->y->coeffErr[i][j] = input->y->coeffErr[i][j];
+        }
+    }
+    return (output);
+}
+
+psProjection *psProjectionCopy (psProjection *input) {
+
+    psProjection *output = psProjectionAlloc (input->R, input->D, input->Xs, input->Ys, input->type);
+    return (output);
+}
+
+// very crude distortion inversion: assumes 0 order in z and t, linear in x and y:
+psPlaneDistort *psPlaneDistortInvert(psPlaneDistort *distort) {
+    PS_ASSERT_PTR_NON_NULL(distort, 0);
+    PS_ASSERT_PTR_NON_NULL(distort->x, 0);
+    PS_ASSERT_PTR_NON_NULL(distort->y, 0);
+
+    psPlaneDistort *out = psPlaneDistortAlloc(1, 1, 0, 0);
+
+    /* simple matrix inversion code */
+
+    psF64 r11 = distort->x->coeff[1][0][0][0];
+    psF64 r12 = distort->x->coeff[0][1][0][0];
+    psF64 r21 = distort->y->coeff[1][0][0][0];
+    psF64 r22 = distort->y->coeff[0][1][0][0];
+    psF64 xo  = distort->x->coeff[0][0][0][0];
+    psF64 yo  = distort->y->coeff[0][0][0][0];
+
+    psF64 invDet = 1.0 / (r11 * r22 - r12 * r21); // Inverse of the determinant
+
+    out->x->coeff[1][0][0][0] = +invDet * r22;
+    out->x->coeff[0][1][0][0] = -invDet * r12;
+    out->y->coeff[1][0][0][0] = -invDet * r21;
+    out->y->coeff[0][1][0][0] = +invDet * r11;
+
+    out->x->coeff[0][0][0][0] = - invDet * (r22 * xo - r12 * yo);
+    out->y->coeff[0][0][0][0] = - invDet * (r11 * yo - r21 * xo);
+
+    return(out);
+}
+
+// returns the rotation term, forcing positive parity
+double psPlaneTransformGetRotation (psPlaneTransform *map) {
+
+    if (map->x->nX < 1) return 0;
+    if (map->x->nY < 1) return 0;
+
+    if (map->y->nX < 1) return 0;
+    if (map->y->nY < 1) return 0;
+
+    double pc1_1 = map->x->coeff[1][0];
+    double pc1_2 = map->x->coeff[0][1];
+    double pc2_1 = map->y->coeff[1][0];
+    double pc2_2 = map->y->coeff[0][1];
+
+    double px = SIGN (pc1_1);
+    double py = SIGN (pc2_2);
+
+    // both x and y terms imply an angle. take the average
+    double t1 = -atan2 (px*pc1_2, px*pc1_1);
+    double t2 = +atan2 (py*pc2_1, py*pc2_2);
+
+    // careful near -pi,+pi boundary...
+    if (t1 - t2 > M_PI/2) t2 += 2*M_PI;
+    if (t2 - t1 > M_PI/2) t1 += 2*M_PI;
+
+    double theta = 0.5*(t1 + t2);
+    while (theta < M_PI) theta += 2*M_PI;
+    while (theta > M_PI) theta -= 2*M_PI;
+
+    return (theta);
+}
+
+# endif
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroVersion.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroVersion.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroVersion.c	(revision 21632)
@@ -0,0 +1,27 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psastro.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString psastroVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString psastroVersionLong(void)
+{
+    psString version = psastroVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+    psFree(tag);
+    return version;
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroWCS.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroWCS.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psastro/src/psastroWCS.c	(revision 21632)
@@ -0,0 +1,577 @@
+# include "psastro.h"
+
+// interpret header WCS (only handles traditional WCS for the moment)
+// plateScale is nominal physical scale on tangent plane (microns / arcsecond)
+bool pmAstromReadWCS (pmFPA *fpa, pmChip *chip, psMetadata *header, double plateScale, bool isMosaic) { 
+
+    psProjectionType type;
+    bool status, pcKeys, cdKeys;
+    float crval1, crval2, crpix1, crpix2, cdelt1, cdelt2;
+    float pc1_1, pc1_2, pc2_1, pc2_2;
+
+    // interpret header data, convert to crval(i), etc
+    char *ctype = psMetadataLookupPtr (&status, header, "CTYPE2");
+    if (!status) {
+	psLogMsg ("psastro", 2, "warning: no WCS metadata in header\n");
+	return false;
+    }
+
+    // determine projection type
+    // XXX add the Elixir DIS / WRP two-layer projection here
+    type = PS_PROJ_NTYPE;
+    if (!strcmp (&ctype[4], "-SIN")) type = PS_PROJ_SIN;
+    if (!strcmp (&ctype[4], "-TAN")) type = PS_PROJ_TAN;
+    if (!strcmp (&ctype[4], "-AIT")) type = PS_PROJ_AIT;
+    if (!strcmp (&ctype[4], "-PAR")) type = PS_PROJ_PAR;
+    if (type == PS_PROJ_NTYPE) {
+	psLogMsg ("psastro", 2, "warning: unknown projection type %s\n", ctype);
+	return false;
+    }
+
+    // what type of WCS keywords are available?
+    psMetadataLookupF32 (&pcKeys, header, "PC001001");
+    psMetadataLookupF32 (&cdKeys, header, "CD1_1");
+
+    if (cdKeys && pcKeys) {
+	psLogMsg ("psastro", 2, "warning: both CDi_j and PC00i00j defined in headers, using CDi_j terms\n");
+    }
+    if (!cdKeys && !pcKeys) {
+        psError(PS_ERR_UNKNOWN, true, "missing both CDi_j and PC00i00j WCS terms");
+	// XXX we could default here to RA, DEC, ROTANGLE
+	return false;
+    }
+
+    crval1 = psMetadataLookupF32 (&status, header, "CRVAL1");
+    crval2 = psMetadataLookupF32 (&status, header, "CRVAL2");
+    crpix1 = psMetadataLookupF32 (&status, header, "CRPIX1");
+    crpix2 = psMetadataLookupF32 (&status, header, "CRPIX2");
+    
+    // test the CDELTi varient
+    if (pcKeys) {
+	cdelt1 = psMetadataLookupF32 (&status, header, "CDELT1");
+	cdelt2 = psMetadataLookupF32 (&status, header, "CDELT2");
+
+	// test the CROTAi varient:
+	double rotate = psMetadataLookupF32 (&status, header, "CROTA2");
+	if (status) {
+	    double Lambda = cdelt2 / cdelt1;
+	    pc1_1 =  cos(rotate*RAD_DEG);
+	    pc1_2 = -sin(rotate*RAD_DEG) * Lambda;
+	    pc2_1 =  sin(rotate*RAD_DEG) / Lambda;
+	    pc2_2 =  cos(rotate*RAD_DEG);
+	    goto got_matrix;
+	}
+
+	// test the PC00i00j varient:
+	pc1_1 = psMetadataLookupF32 (&status, header, "PC001001");
+	if (status) {
+	    pc1_2 = psMetadataLookupF32 (&status, header, "PC001002");
+	    pc2_1 = psMetadataLookupF32 (&status, header, "PC002001");
+	    pc2_2 = psMetadataLookupF32 (&status, header, "PC002002");
+
+	    // XXX EAM : add Elixir polynomial terms here eventually
+	    goto got_matrix;
+	}
+	psLogMsg ("psastro", 2, "warning: missing rotation matrix?\n");
+	return false;
+    }
+
+    // test the CDi_j varient
+    if (cdKeys) {
+	pc1_1 = psMetadataLookupF32 (&status, header, "CD1_1");
+	pc1_2 = psMetadataLookupF32 (&status, header, "CD1_2");
+	pc2_1 = psMetadataLookupF32 (&status, header, "CD2_1");
+	pc2_2 = psMetadataLookupF32 (&status, header, "CD2_2");
+	
+	// renormalize to cdelt1, cdelt2, etc
+	double scale = hypot (pc1_1, pc1_2);
+	cdelt1 = cdelt2 = scale;
+	pc1_1 /= scale;
+	pc1_2 /= scale;
+	pc2_1 /= scale;
+	pc2_2 /= scale;
+	goto got_matrix;
+    }
+    psLogMsg ("psastro", 2, "warning: missing rotation matrix?\n");
+    return false;
+
+got_matrix:
+
+    /*****
+
+    For mosaic astrometry, we need to have a starting set of projection terms in which the
+    chip-to-FPA terms result in a fixed physical unit on the focal plane (eg, pixels or
+    microns).  This set of projections, coupled with an identity toTPA (ie, no distortion) will
+    result in substantial errors between the observed and predicted star positions on the focal
+    plane: this is the measurement of the optical distortion in the camera.  At the same time,
+    we need to carry around the transformations which allow us to make an accurate calculation
+    of the position of the stars based on the input (per-chip) astrometry.  These
+    transformations will allow us to match the raw and ref stars robustly.  To convert the
+    per-chip astrometry (which may have been calculated with a different plate scale for each
+    chip) to a collection of astrometry terms for chips in a single mosaic, we need to adjust
+    the chip-to-FPA scaling (eg, pc11) to match the variations in the effective plate scale for
+    each chip (eg, cdelt1).  Thus, we need to carry around both the
+
+    *****/
+
+    {
+	double rX = 1.0;
+	double rY = 1.0;
+
+	// XXX free an existing toFPA?
+	psPlaneTransform *toFPA = psPlaneTransformAlloc (1, 1);
+    
+	// basic transformation from chip to FPA
+	toFPA->x->coeff[0][0] = -(pc1_1*crpix1 + pc1_2*crpix2);
+	toFPA->x->coeff[1][0] = pc1_1;
+	toFPA->x->coeff[0][1] = pc1_2;
+	toFPA->x->mask[1][1]  = 1;
+
+	toFPA->y->coeff[0][0] = -(pc2_1*crpix1 + pc2_2*crpix2);
+	toFPA->y->coeff[1][0] = pc2_1;
+	toFPA->y->coeff[0][1] = pc2_2;
+	toFPA->y->mask[1][1]  = 1;
+
+	// projection from TPA to SKY
+	psProjection *toSky = psProjectionAlloc (crval1*RAD_DEG, crval2*RAD_DEG, cdelt1*RAD_DEG, cdelt2*RAD_DEG, type);
+
+	if (fpa->toSky == NULL) {
+	    // XXX for now, use the identity for TPA <--> FPA
+	    fpa->toTPA = psPlaneDistortIdentity (1);
+	    fpa->fromTPA = psPlaneDistortIdentity (1);
+	    fpa->toSky = toSky;
+	} else {
+	    if (fpa->toTPA == NULL) psAbort("projection defined, tangent-plane not defined");
+	    if (fpa->fromTPA == NULL) psAbort("projection defined, tangent-plane not defined");
+
+	    // adjust for common toSky for mosaic:
+	    // ignore the TPA since toTPA is identity
+	    // find the FPA coordinate of 0,0 for this chip.
+	    psPlane *fp = psPlaneAlloc();
+	    psPlane *chip = psPlaneAlloc();
+	    psSphere *sky = psSphereAlloc();
+	    chip->x = chip->y = 0;
+	
+	    psPlaneTransformApply (fp, toFPA, chip); // find the focal-plane coordinate of this chip's 0,0 coordinate
+	    psDeproject (sky, fp, toSky); // find the RA,DEC coord of the focal-plane coordinate
+	    psProject (fp, sky, fpa->toSky); // find the focal-plane coord of this RA,DEC coord using the ref chip projection
+
+	    toFPA->x->coeff[0][0] = fp->x;
+	    toFPA->y->coeff[0][0] = fp->y;
+
+	    rX = toSky->Xs/fpa->toSky->Xs;
+	    rY = toSky->Ys/fpa->toSky->Ys;
+
+	    // correct to common plate scale
+	    toFPA->x->coeff[1][0] *= rX;
+	    toFPA->x->coeff[0][1] *= rX;
+	    toFPA->y->coeff[1][0] *= rY;
+	    toFPA->y->coeff[0][1] *= rY;
+
+	    psFree (fp);
+	    psFree (sky);
+	    psFree (chip);
+	    psFree (toSky);
+	}
+
+	chip->toFPA = toFPA;
+	chip->fromFPA = p_psPlaneTransformLinearInvert(toFPA);
+
+	while (fpa->toSky->R <        0) fpa->toSky->R += 2.0*M_PI;
+	while (fpa->toSky->R > 2.0*M_PI) fpa->toSky->R -= 2.0*M_PI;
+
+	// remove the correction to the common plate scale
+	if (isMosaic) {
+	    chip->toFPA->x->coeff[1][0] /= rX;
+	    chip->toFPA->x->coeff[0][1] /= rX;
+	    chip->toFPA->y->coeff[1][0] /= rY;
+	    chip->toFPA->y->coeff[0][1] /= rY;
+	}
+
+	psTrace ("psastro", 5, "toFPA: %f %f  (%f,%f),(%f,%f)\n", 
+		 chip->toFPA->x->coeff[0][0], chip->toFPA->y->coeff[0][0], 
+		 chip->toFPA->x->coeff[1][0], chip->toFPA->x->coeff[0][1], 
+		 chip->toFPA->y->coeff[1][0], chip->toFPA->y->coeff[0][1]);
+
+	psTrace ("psastro", 5, "frFPA: %f %f  (%f,%f),(%f,%f)\n", 
+		 chip->fromFPA->x->coeff[0][0], chip->fromFPA->y->coeff[0][0], 
+		 chip->fromFPA->x->coeff[1][0], chip->fromFPA->x->coeff[0][1], 
+		 chip->fromFPA->y->coeff[1][0], chip->fromFPA->y->coeff[0][1]);
+
+	psLogMsg ("psastro", 3, "field center: %f,%f, plate scale: %f,%f (arcsec/pixel)\n", 
+		  DEG_RAD*fpa->toSky->R, DEG_RAD*fpa->toSky->D, 
+		  3600*DEG_RAD*fpa->toSky->Xs, 3600*DEG_RAD*fpa->toSky->Ys);
+    }
+    return true;
+}
+
+// convert toFPA / toSky components to traditional WCS
+// plateScale is nominal physical scale on tangent plane (microns / arcsecond)
+// this requires toTP to be the identity transformation
+bool pmAstromWriteWCS (psPlaneTransform *toFPA, psPlaneDistort *toTPA, psProjection *toSky, psMetadata *header, double plateScale) { 
+
+    // techinically, we can have a plate scale here (toTPA:dx,dy != 1)
+    if (!psPlaneDistortIsIdentity (toTPA)) psAbort("invalid TPA transformation");
+    
+    // XXX require toFPA->x->nX == toFPA->x->nY
+    // XXX require toFPA->y->nX == toFPA->y->nY
+    // XXX require toFPA->x->nX == toFPA->y->nX
+    // XXX require toFPA->nX == 1,2,3
+
+    switch (toSky->type) {
+      case PS_PROJ_SIN:
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---SIN");
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--SIN");
+	break;
+      case PS_PROJ_TAN:
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---TAN");
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--TAN");
+	break;
+      case PS_PROJ_AIT:
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---AIT");
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--AIT");
+	break;
+      case PS_PROJ_PAR:
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---PAR");
+	psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--PAR");
+	break;
+      default:
+	psLogMsg ("psastro", 2, "warning: unknown projection type %d\n", toSky->type);
+	return false;
+    }
+
+# if (0)
+    // XXX not really right: needs to deal with non-identity to coeffs
+    // XXX actually, totally wrong.  fix the conversions
+    // XXX need to handle the plateScale
+    
+    /* discussion of the coord transformations:
+    X,Y: coord on a chip in pixels
+    L,M: coord on the focal plane (pixels)
+    P,Q: coord in the tangent plane (microns or mm?)
+    R,D: coord on the sky 
+    
+    this function creates WCS terms which convert directly from chip to sky.
+    this function requires a linear, unrotated toTPA distortion term
+    toTPA->x,y->coeff[1][0],[0][1] defines the detector scale (microns / pixel)
+    tpSky->Xs,Ys defines the plate scale (radians / micron)
+    */
+    
+    // solve for CDELT1,2 (degrees / pixel)
+    cdelt1 = DEG_RAD*toSky->Xs*toTPA->x->coeff[1][0][0][0];
+    cdelt2 = DEG_RAD*toSky->Ys*toTPA->y->coeff[0][1][0][0];
+
+    // L,M = toFPA(X,Y)
+    // solve for CRPIX1,2 (Xo,Yo) : L,M(Xo,Yo) = 0,0
+
+    // linear solution for Xo,Yo:
+    xcoeff = toFPA->x->coeff;
+    ycoeff = toFPA->y->coeff;
+    R  = (xcoeff[1][0]*ycoeff[0][1] - xcoeff[0][1]*ycoeff[1][0]);
+    Xo = det*(ycoeff[0][0]*xcoeff[0][1] - xcoeff[0][0]*ycoeff[0][1]);
+    Yo = det*(xcoeff[0][0]*ycoeff[1][0] - ycoeff[0][0]*xcoeff[1][0]);
+
+    if (toFPA->x->nX > 1) {
+
+	psPolynomial2D *XdX = psPolynomial2D_dX(toFPA->x);
+	psPolynomial2D *XdY = psPolynomial2D_dY(toFPA->x);
+
+	psPolynomial2D *YdX = psPolynomial2D_dX(toFPA->y);
+	psPolynomial2D *YdY = psPolynomial2D_dY(toFPA->y);
+
+	psImage *Alpha = psImageAlloc (2, 2, PS_DATA_F32);
+	psVector *Beta = psVectorAlloc (2, PS_DATA_F32);
+
+	// XXX this loop is rather arbitrary in length...
+	// XXX measure the error and use as criterion
+	for (int i = 0; i < 10; i++) {
+	    // NOTE: order is: [y][x]
+	    Alpha->data.F32[0][0] = psPolynomial2DEval (XdX, Xo, Yo);
+	    Alpha->data.F32[1][0] = psPolynomial2DEval (XdY, Xo, Yo);
+	    Alpha->data.F32[0][1] = psPolynomial2DEval (YdX, Xo, Yo);
+	    Alpha->data.F32[1][1] = psPolynomial2DEval (YdY, Xo, Yo);
+
+	    Beta->data.F32[0] = psPolynomial2DEval (toFPA->x, Xo, Yo);
+	    Beta->data.F32[1] = psPolynomial2DEval (toFPA->y, Xo, Yo);
+
+	    psMatrixGJSolveF32 (Alpha, Beta);
+	
+	    Xo += Beta->data.F32[0];
+	    Yo += Beta->data.F32[1];
+	}
+    }
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", 	PS_META_REPLACE, "", Xo);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", 	PS_META_REPLACE, "", Yo);
+
+    psPolynomial2D *xWCS = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, toFPA->x->nX, toFPA->x->nY);
+    psPolynomial2D *yWCS = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, toFPA->y->nX, toFPA->y->nY);
+
+    psPolynomial2D *xPx = psPolynomial2DCopy (toFPA->x);
+    psPolynomial2D *yPx = psPolynomial2DCopy (toFPA->y);
+
+    // skip the zero order terms
+    // XXX double check that these relationships are correct
+    for (int i = 0; i < toFPA->x->nX; i++) {
+	psPolynomial2D *xPy = psPolynomial2DCopy (xPx);
+	psPolynomial2D *yPy = psPolynomial2DCopy (yPx);
+	for (int j = 0; j < toFPA->x->nY; j++) {
+	    xWCS->coords[i][j] = psPolynomial2DEval (xPy, Xo, Yo) / (i*j) / pow(cdelt1, i) / pow(cdelt2, j);
+	    yWCS->coords[i][j] = psPolynomial2DEval (yPy, Xo, Yo) / (i*j) / pow(cdelt1, i) / pow(cdelt2, j);
+	    psPolynomial2D_dY(xPy, xPy);
+	    psPolynomial2D_dY(yPy, yPy);
+	}
+	psPolynomial2D_dX(xPx, xPx);
+	psPolynomial2D_dX(yPx, yPx);
+    }
+
+    while (coords[0].crval1 < 0) coords[0].crval1 += 360.0;
+    while (coords[0].crval1 > 360.0) coords[0].crval1 -= 360.0;
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", 	PS_META_REPLACE, "", toSky->R*DEG_RAD);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", 	PS_META_REPLACE, "", toSky->D*DEG_RAD);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", 	PS_META_REPLACE, "", Xo);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", 	PS_META_REPLACE, "", Yo);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1", 	PS_META_REPLACE, "", cdelt1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2", 	PS_META_REPLACE, "", cdelt2);
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", xWCS->coeff[1][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", xWCS->coeff[0][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", yWCS->coeff[1][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", yWCS->coeff[0][1]);
+
+    // XXX respect the masks
+    for (int i = 0; i < xWCS->nX; i++) {
+	for (int j = 0; j < xWCS->nX; j++) {
+	    if (i + j < 2) continue;
+	    sprintf (name, "PCA1dX%1dY%1d", i, j);
+	    psMetadataAddF32 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", xWCS->coeff[i][j]);
+	}
+    }
+    for (int i = 0; i < yWCS->nX; i++) {
+	for (int j = 0; j < yWCS->nX; j++) {
+	    if (i + j < 2) continue;
+	    sprintf (name, "PCA2dX%1dY%1d", i, j);
+	    psMetadataAddF32 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", yWCS->coeff[i][j]);
+	}
+    }
+	    
+# else 
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", 	PS_META_REPLACE, "", toSky->R*DEG_RAD);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", 	PS_META_REPLACE, "", toSky->D*DEG_RAD);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", 	PS_META_REPLACE, "", toFPA->x->coeff[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", 	PS_META_REPLACE, "", toFPA->y->coeff[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1", 	PS_META_REPLACE, "", toSky->Xs*DEG_RAD*plateScale);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2", 	PS_META_REPLACE, "", toSky->Ys*DEG_RAD*plateScale);
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", toFPA->x->coeff[1][0]/plateScale);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", toFPA->x->coeff[0][1]/plateScale);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", toFPA->y->coeff[1][0]/plateScale);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", toFPA->y->coeff[0][1]/plateScale);
+
+# endif
+
+    // alternative representations use
+    // CD1_1 = PC001001*CDELT1, etc
+    // make these representations optional
+
+    return true;
+}
+
+// convert toFPA / toSky components to traditional WCS
+// plateScale is nominal physical scale on tangent plane (microns / arcsecond)
+bool pmAstromWriteBilevelChip (psPlaneTransform *toFPA, psMetadata *header, double plateScale) { 
+
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---WRP");
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--WRP");
+
+    // XXX not really right: needs to deal with non-identity toTP coeffs & plateScale
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", 	PS_META_REPLACE, "", toFPA->x->coeff[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", 	PS_META_REPLACE, "", toFPA->y->coeff[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", 	PS_META_REPLACE, "", 0.0);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", 	PS_META_REPLACE, "", 0.0);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1", 	PS_META_REPLACE, "", 1.0);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2", 	PS_META_REPLACE, "", 1.0);
+
+    if (toFPA->x->nX != toFPA->x->nY) psAbort("mis-matched tangent plane orders (1)");
+    if (toFPA->x->nX != toFPA->y->nX) psAbort("mis-matched tangent plane orders (2)");
+    if (toFPA->x->nX != toFPA->y->nY) psAbort("mis-matched tangent plane orders (3)");
+
+    switch (toFPA->x->nX) {
+      case 3:
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X3Y0", PS_META_REPLACE, "", toFPA->x->coeff[3][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y1", PS_META_REPLACE, "", toFPA->x->coeff[2][1]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y2", PS_META_REPLACE, "", toFPA->x->coeff[1][2]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y3", PS_META_REPLACE, "", toFPA->x->coeff[0][3]);
+
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X3Y0", PS_META_REPLACE, "", toFPA->y->coeff[3][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y1", PS_META_REPLACE, "", toFPA->y->coeff[2][1]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y2", PS_META_REPLACE, "", toFPA->y->coeff[1][2]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y3", PS_META_REPLACE, "", toFPA->y->coeff[0][3]);
+
+      case 2:
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y0", PS_META_REPLACE, "", toFPA->x->coeff[2][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y1", PS_META_REPLACE, "", toFPA->x->coeff[1][1]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y2", PS_META_REPLACE, "", toFPA->x->coeff[0][2]);
+
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y0", PS_META_REPLACE, "", toFPA->y->coeff[2][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y1", PS_META_REPLACE, "", toFPA->y->coeff[1][1]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y2", PS_META_REPLACE, "", toFPA->y->coeff[0][2]);
+
+      case 1:
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", toFPA->x->coeff[1][0]/plateScale);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", toFPA->x->coeff[0][1]/plateScale);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", toFPA->y->coeff[1][0]/plateScale);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", toFPA->y->coeff[0][1]/plateScale);
+	break;
+
+      case 0:
+	psAbort("invalid tangent plane order");
+    }
+    return true;
+}
+
+// convert toFPA / toSky components to traditional WCS
+// plateScale is nominal physical scale on tangent plane (microns / arcsecond)
+psMetadata *pmAstromWriteBilevelMosaic (psProjection *toSky, psPlaneDistort *toTP, double plateScale) { 
+
+    psMetadata *header = psMetadataAlloc ();
+
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---DIS");
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--DIS");
+
+    // XXX need to handle the plateScale??
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", 	PS_META_REPLACE, "", toSky->R*DEG_RAD);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", 	PS_META_REPLACE, "", toSky->D*DEG_RAD);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", 	PS_META_REPLACE, "", 0.0);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", 	PS_META_REPLACE, "", 0.0);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1", 	PS_META_REPLACE, "", toSky->Xs*DEG_RAD*plateScale);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2", 	PS_META_REPLACE, "", toSky->Ys*DEG_RAD*plateScale);
+
+    if (toTP->x->nX != toTP->x->nY) psAbort("mis-matched tangent plane orders (1)");
+    if (toTP->x->nX != toTP->y->nX) psAbort("mis-matched tangent plane orders (2)");
+    if (toTP->x->nX != toTP->y->nY) psAbort("mis-matched tangent plane orders (3)");
+
+    switch (toTP->x->nX) {
+      case 3:
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X3Y0", PS_META_REPLACE, "", toTP->x->coeff[3][0][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y1", PS_META_REPLACE, "", toTP->x->coeff[2][1][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y2", PS_META_REPLACE, "", toTP->x->coeff[1][2][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y3", PS_META_REPLACE, "", toTP->x->coeff[0][3][0][0]);
+
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X3Y0", PS_META_REPLACE, "", toTP->y->coeff[3][0][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y1", PS_META_REPLACE, "", toTP->y->coeff[2][1][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y2", PS_META_REPLACE, "", toTP->y->coeff[1][2][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y3", PS_META_REPLACE, "", toTP->y->coeff[0][3][0][0]);
+
+      case 2:
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y0", PS_META_REPLACE, "", toTP->x->coeff[2][0][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y1", PS_META_REPLACE, "", toTP->x->coeff[1][1][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y2", PS_META_REPLACE, "", toTP->x->coeff[0][2][0][0]);
+
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y0", PS_META_REPLACE, "", toTP->y->coeff[2][0][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y1", PS_META_REPLACE, "", toTP->y->coeff[1][1][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y2", PS_META_REPLACE, "", toTP->y->coeff[0][2][0][0]);
+
+      case 1:
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", toTP->x->coeff[1][0][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", toTP->x->coeff[0][1][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", toTP->y->coeff[1][0][0][0]);
+	psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", toTP->y->coeff[0][1][0][0]);
+	break;
+
+      case 0:
+	psAbort("invalid tangent plane order");
+    }
+    return header;
+}
+
+// construct a psPlaneDistort which is the identify transformation
+psPlaneDistort *psPlaneDistortIdentity (int order) {
+
+    psPlaneDistort *distort;
+
+    if (order < 1) psAbort("invalid order");
+    if (order > 3) psAbort("invalid order");
+    
+    // all coeffs and masks initially set to 0
+    distort = psPlaneDistortAlloc (order, order, 0, 0);
+
+    for (int i = 0; i <= order; i++) {
+	for (int j = 0; j <= order; j++) {
+	    if (i + j > order) {
+		distort->x->mask [i][j][0][0] = 1;
+		distort->y->mask [i][j][0][0] = 1;
+	    } 
+	}
+    }
+    distort->x->coeff[1][0][0][0] = 1;
+    distort->y->coeff[0][1][0][0] = 1;
+
+    return distort;
+}
+
+// check that the given psPlaneDistort is the identity
+bool psPlaneDistortIsIdentity (psPlaneDistort *distort) {
+
+    int order;
+    bool status;
+
+    // we currently only support up to 3rd order polynomials
+    if (distort->x->nX < 1) return false;
+    if (distort->x->nY < 1) return false;
+    if (distort->y->nX < 1) return false;
+    if (distort->y->nY < 1) return false;
+
+    if (distort->x->nX > 3) return false;
+    if (distort->x->nY > 3) return false;
+    if (distort->y->nX > 3) return false;
+    if (distort->y->nY > 3) return false;
+    
+    if (distort->x->nZ > 0) return false;
+    if (distort->x->nT > 0) return false;
+    if (distort->y->nZ > 0) return false;
+    if (distort->y->nT > 0) return false;
+    
+    if (distort->x->nX != distort->x->nY) return false;
+    if (distort->y->nX != distort->y->nY) return false;
+
+    status = true;
+    order = distort->x->nX;
+    for (int i = 0; i <= order; i++) {
+	for (int j = 0; j <= order; j++) {
+	    if (i + j > order) {
+		// high-order cross terms must be masked (eg, x^3 y^2)
+		status &= !distort->x->mask[i][j][0][0];
+	    } else {
+		if (i + j != 1) {
+		    // non-linear and off-diagonal terms must be 0 (eg, x^2, x y)
+		    status &= (distort->x->coeff[i][j][0][0] == 0.0);
+		} else {
+		    // linear, diagonal terms must be 1.0
+		    status &= (distort->x->coeff[i][j][0][0] == 1.0);
+		}
+	    }
+	}
+    }
+
+    order = distort->y->nX;
+    for (int i = 0; i <= order; i++) {
+	for (int j = 0; j <= order; j++) {
+	    if (i + j > order) {
+		// high-order cross terms must be masked (eg, x^3 y^2)
+		status &= !distort->y->mask[i][j][0][0];
+	    } else {
+		if (i + j != 1) {
+		    // non-linear and off-diagonal terms must be 0 (eg, x^2, x y)
+		    status &= (distort->y->coeff[i][j][0][0] == 0.0);
+		} else {
+		    // linear, diagonal terms must be 1.0
+		    status &= (distort->y->coeff[i][j][0][0] == 1.0);
+		}
+	    }
+	}
+    }
+    return status;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/.cvsignore
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/.cvsignore	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/.cvsignore	(revision 21632)
@@ -0,0 +1,18 @@
+bin
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+libtool
+ltmain.sh
+stamp-h1
+config.sub
+psphot.pc
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/Makefile.am
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/Makefile.am	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/Makefile.am	(revision 21632)
@@ -0,0 +1,10 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psphot.pc
+
+EXTRA_DIST = \
+	psphot.pc.in \
+	autogen.sh
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/autogen.sh
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/autogen.sh	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/autogen.sh	(revision 21632)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=psphot
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/configure.ac
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/configure.ac	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/configure.ac	(revision 21632)
@@ -0,0 +1,215 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.59)
+
+AC_INIT([psphot], [0.9.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------------ kapa,libkapa options -------------------------
+dnl -- libkapa implies the requirement for libpng, libjpeg as well --
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+dnl test for command-line options: use ohana-config if not supplied
+KAPA_CFLAGS_CONFIG="true"
+KAPA_LIBS_CONFIG="true"
+AC_ARG_WITH(kapa,
+[AS_HELP_STRING(--with-kapa=DIR,Specify location of libkapa)],
+[KAPA_CFLAGS="-I$withval/include" KAPA_LIBS="-L$withval/lib" 
+ KAPA_CFLAGS_CONFIG="false"       KAPA_LIBS_CONFIG="false"])
+AC_ARG_WITH(kapa-include,
+[AS_HELP_STRING(--with-kapa-include=DIR,Specify libkapa include directory.)],
+[KAPA_CFLAGS="-I$withval" KAPA_CFLAGS_CONFIG="false"])
+AC_ARG_WITH(kapa-lib,
+[AS_HELP_STRING(--with-kapa-lib=DIR,Specify libkapa library directory.)],
+[KAPA_LIBS="-L$withval" KAPA_LIBS_CONFIG="false"])
+
+echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+echo "KAPA_LIBS_CONFIG: $KAPA_LIBS_CONFIG"
+echo "KAPA_CFLAGS: $KAPA_CFLAGS"
+echo "KAPA_LIBS: $KAPA_LIBS"
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+HAVE_KAPA="true"
+AC_MSG_NOTICE([checking for libkapa])
+if test "$KAPA_CFLAGS_CONFIG" = "true" -o "$KAPA_LIBS_CONFIG" = "true"; then
+  AC_MSG_NOTICE([kapa info supplied by ohana-config])
+  KAPA_CONFIG=`which ohana-config`
+  AC_CHECK_FILE($KAPA_CONFIG,[],
+    [HAVE_KAPA="false"; AC_MSG_WARN([libkapa is not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location])])
+  
+  echo "HAVE_KAPA: $HAVE_KAPA"
+  echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_CFLAGS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa cflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa cflags])
+   KAPA_CFLAGS="`${KAPA_CONFIG} --cflags`"
+   AC_MSG_RESULT([${KAPA_CFLAGS}])
+  fi
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_LIBS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa ldflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa ldflags])
+   KAPA_LIBS="`${KAPA_CONFIG} --libs` -lX11"
+   AC_MSG_RESULT([${KAPA_LIBS}])
+  fi
+fi
+
+if test "$HAVE_KAPA" = "true" ; then
+ AC_MSG_NOTICE([libkapa supplied])
+ PSPHOT_CFLAGS="${PSPHOT_CFLAGS} ${KAPA_CFLAGS}"
+ PSPHOT_LIBS="${PSPHOT_LIBS} ${KAPA_LIBS}"
+else
+ AC_MSG_NOTICE([libkapa ignored])
+fi
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+dnl HAVE_KAPA=true
+dnl AC_CHECK_HEADERS([kapa.h],
+dnl  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $KAPA_CFLAGS" AC_SUBST(KAPA_CFLAGS)],
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])]
+dnl )
+dnl AC_CHECK_LIB(kapa,KapaInitGraph,
+dnl  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],  
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])],[-lm]
+dnl )
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libjpeg options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(jpeg,
+[AS_HELP_STRING(--with-jpeg=DIR,Specify location of libjpeg.)],
+[JPEG_CFLAGS="-I$withval/include"
+ JPEG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(jpeg-include,
+[AS_HELP_STRING(--with-jpeg-include=DIR,Specify libjpeg include directory.)],
+[JPEG_CFLAGS="-I$withval"])
+AC_ARG_WITH(jpeg-lib,
+[AS_HELP_STRING(--with-jpeg-lib=DIR,Specify libjpeg library directory.)],
+[JPEG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${JPEG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${JPEG_LDFLAGS}"
+
+AC_CHECK_HEADERS([jpeglib.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $JPEG_CFLAGS" AC_SUBST(JPEG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg headers not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg library not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libpng options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(png,
+[AS_HELP_STRING(--with-png=DIR,Specify location of libpng.)],
+[PNG_CFLAGS="-I$withval/include"
+ PNG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(png-include,
+[AS_HELP_STRING(--with-png-include=DIR,Specify libpng include directory.)],
+[PNG_CFLAGS="-I$withval"])
+AC_ARG_WITH(png-lib,
+[AS_HELP_STRING(--with-png-lib=DIR,Specify libpng library directory.)],
+[PNG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${PNG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${PNG_LDFLAGS}"
+
+AC_CHECK_HEADERS([png.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $PNG_CFLAGS" AC_SUBST(PNG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng headers not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+AC_CHECK_LIB(png,png_init_io,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $PNG_LDFLAGS -lpng"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng library not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ use kapa or not? ---------------------
+
+if test "$HAVE_KAPA" == "true" ; then
+  AC_MSG_RESULT([including plotting functions])
+  AC_DEFINE([HAVE_KAPA],[1],[enable use of libkapa])
+else
+  AC_MSG_RESULT([skipping plotting functions])
+  AC_DEFINE([HAVE_KAPA],[0],[disable use of libkapa])
+fi
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+
+CFLAGS="${CFLAGS=} -Wall -Werror -std=c99"
+echo "PSPHOT_CFLAGS: $PSPHOT_CFLAGS"
+echo "PSPHOT_LIBS: $PSPHOT_LIBS"
+
+AC_SUBST([PSPHOT_CFLAGS])
+AC_SUBST([PSPHOT_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  psphot.pc
+])
+
+AC_OUTPUT
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/config.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/config.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/config.txt	(revision 21632)
@@ -0,0 +1,56 @@
+
+# input data options
+# IMAGE             STR    file.fits
+# MASK_IMAGE	    STR    mask.fits
+# WEIGHT_IMAGE	    STR	   weight.fits
+
+# output data options
+OUTPUT_FILE	    STR	   output.smp    # output object file
+OUTPUT_MODE	    STR	   TEXT          # output mode: TEXT, OBJ, SX, CMP, CMF 
+# RESID_IMAGE	    STR	   resid.fits    # output residual image
+
+# image noise parameters
+RDNOISE             STR  HD:RDNOISE      # read-noise in electrons
+GAIN                STR  HD:GAIN         # electrons / DN
+
+# masking parameters
+XMIN                F32  32        	 # minimum valid x-coord
+XMAX                F32   0        	 # maximum valid x-coord
+YMIN                F32   1        	 # minimum valid y-coord
+YMAX                F32   0        	 # maximum valid y-coord
+SATURATION          F32  50000     	 # saturation level on this chip
+
+# image statistics parameters
+IMSTATS_NPIX        S32  100000    	 # number of pixels to use for sky estimate:
+
+# peak finding 
+PEAKS_SMOOTH_SIGMA  F32  1.0       	 # smoothing kernel sigma in pixels
+PEAKS_SMOOTH_NSIGMA F32  3.0   	   	 # smoothing kernel width in sigmas
+PEAKS_NSIGMA_LIMIT  F32  10.0  	   	 # peak significance threshold
+
+# basic object statistics
+SKY_INNER_RADIUS    F32  15		 # square annulus for local sky measurement
+SKY_OUTER_RADIUS    F32  25		 # square annulus for local sky measurement
+PSF_MOMENTS_RADIUS  F32  5
+PSF_SN_LIM          F32  100
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+#PSF_MODEL           STR  PS_MODEL_PGAUSS
+#PSF_MODEL           STR  PS_MODEL_GAUSS
+PSF_FIT_RADIUS      F32  25		 # fitting radius for test PSF model
+
+# PSF model parameters : apply the PSF model
+PSF_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+PSF_FIT_PADDING      F32  5              # extra annulus to use for fit 
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MIN_SN           F32  2.0		 # reject objects below this significance
+PSF_MAX_CHI          F32  10.0		 # reject objects worse that this
+
+# Galaxy model parameters
+GAL_MODEL            STR  PS_MODEL_SGAUSS
+GAL_MIN_SN           F32  3
+GAL_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+GAL_FIT_PADDING      F32  5              # extra annulus to use for fit 
+GAL_MOMENTS_RADIUS   F32  9
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/notes.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/notes.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/notes.txt	(revision 21632)
@@ -0,0 +1,593 @@
+
+ensemble:
+
+  * first block: 
+    * select sources of interest / skip
+    * generate a guess model
+    * re-measure moments for saturated stars (MOVE)
+    * update peak position (MOVE to peaks)
+    * set the object radius 
+    * build temporary source copies (including pixels, mask, weight)
+
+  * second block:
+    * measure the cross-products and weights
+    * add to the sparse matrix
+
+  * build full-image mask
+  * measure coord weights
+  * build order elements
+
+  * solve for source amplitudes
+  * update models
+
+2006.11.16
+
+  * create psSparseBorder to solve matrix equations which have a large
+    sparse region, with a completely filled border of a smaller number
+    of rows.
+
+  * trying to modify psphotEnsemble to (1) be more general (not just
+    PSFs) and (2) fit for the sky offset.
+
+    - how is the radius for each source set?
+    
+
+2006.11.11
+
+  I am trying to define a complete set of tests to validate the
+  measurement of the photometry compared with the simulated images.
+  Part of my confusion is controlling exactly which corrections are
+  applied to the output magnitudes.  Here is the complete list:
+
+  psfMag_out = psfMag_meas + ApTrend(x,y,0.0,0.0)
+  apMag_out = apMag_meas + growth(radius,refRadius) + f(skyBias,skySat)
+
+  these corrections are modified by the following config settings:
+
+  IGNORE_GROWTH (sets all growth magnitudes to the fitMag value,
+  effectively turning off the growth correction)
+
+  APTREND : set the level of correction
+    NONE     : no correction is fitted
+    CONSTANT : just a single constant (applied to get psfMag_out)
+    SKYBIAS  : the skybias term is fitted (constant applied to psfMag,
+               slope applied apMag)
+  
+2006.11.08
+
+  I have been testing the pmSourceMagnitudes interpolation of the
+  source position when measuing the aperture magnitudes.  I built a
+  test suite for pmSourceMagnitudes and pmGrowthCurve.  even with
+  bicubic interpolation, there are noticable errors for small
+  apertures (ie, small compared to the PSF sigma).  I used a gaussian
+  PSF to make the tests.  it seems that, if the radius is less than 2
+  sigma or so, the interpolated aperture mag is in error by as much as
+  12mmag.  The size of this effect must depend on the smoothing
+  algorithm, the shape of the PSF, the size of the PSF relative to the
+  pixels, and the size of the subpixel interpolation.  I believe the
+  effect is a result of the smoothing introduced by the interpolation:
+  the smoothing pushes the flux in the inner portions of the PSF down
+  and enhances the flux in the outer portions.  thus, there is
+  actually a turn over in the error: at very large values, the error
+  is very small because the aperture contains all of the flux, and the
+  interpolation has little effect.  as the radius shrinks, the error
+  grows (in the sense of M_raw - M_interpolated; ie, the flux in the
+  interpolated source is too large).  around 2-3sigma, the error turns
+  over and becomes negative (too little flux in the interpolated
+  source). 
+
+  The sigmas I used for these tests (1.0, 1.5, 2.0 pix) are
+  appropriate for the range we expect to see from PS1, or any
+  well-sampled detector.  Thus, even though the effect is probably
+  less important for poorer seeing, we have to be careful to avoid it
+  in our Pan-STARRS analysis.
+
+  Currently, the minimum aperture is defined only by the config
+  variable PSF_FIT_PAD, which really implies a padding.  My proposal
+  is to require the minimum aperture to be a number of sigma (an
+  additional parameter).  Then, the formula for the radius would be
+  something like:
+
+  MAX (modelRadiusPSF (PSF_FIT_NSIGMA), PSF_FIT_RADIUS_NSIG) +
+  PSF_FIT_PAD
+
+  with PSF_FIT_RADIUS_NSIG (choose a better name!) set to something
+  like 2 or 3.
+
+2006.11.06
+
+  I have added the aperture interpolation as a component of the
+  pmSourceMagnitudes function.  
+
+  I am trying to make things a bit more organized, with more
+  consistent APIs. 
+
+  * changed pmPSFtry to use the pmSourceMagnitudes API
+  * changed pmPSFtry to use new pmSource entries for each try (saving
+  * the pmModels generated)
+
+  other notes:
+  * moved psphotGrowthCurve -> pmGrowthCurveGenerate 
+
+  things I know need to be added / fixed:
+  * measure bicube fit to peak from smoothed image
+  * use psphotEnsemble fit to re-measure normalization?
+  * extended object statistical measures 
+
+2006.11.02
+
+  Looking into the growth curve problem.  RHL sent his recipe for
+  correcting to true circular apertures.  I've done a test fix of
+  interpolating the stellar image to the desired center.  it looks
+  like bilinear interpolation is insufficient (about 8% error for 2
+  pixel radius aperture).  Unless I've got the formula wrong, the
+  effective smoothing of the gaussian is moving flux from the center
+  out and making small apertures in error that depends on the offset.
+  Bicubic could be enough to clean this up?  
+
+  I think the current code may need to be careful modified to include
+  the interpolation for every aperture measurement.  there are paired
+  'pmModelAdd' and pmModelSub steps to add and remove the model flux.
+  I need to be careful about how the mask affects these steps.
+
+  I also need to be careful in interpreting the test results: since
+  the simulated images create objects by filling in a pixel and then
+  smoothing, all objects have centroids which are exactly 0.5 pixel
+  offsets.  These all then have a fixed growth correction error, which
+  fortunately happens to be substantial (because of a 0.5 pixel offset
+  in the growth curve reference object!)
+
+  I need to double check the 0.5 pixel error issues.  There are
+  problems with the pslib version of psImageShift and
+  psImageInterpolate (or at least inconsistencies).  the pmModelAdd
+  and Sub functions and the centroid functions are probably also
+  inconsistent.
+
+2006.11.01
+
+  Trying to understand low-level errors in psphot using simulated
+  data.  the main issue I am concerned with is the bias RHL is seeing
+  in SDSS-psphot comparisons.  But there are other issues that are
+  creeping in and probably point at logical errors in the code.
+
+  * in pmPSFtry, I measure the first pass on the aperture residual (to
+    choose between PSF model options, if that is being tested).  This
+    analysis currently uses a polynomial fit (1st order) for the offset
+    and the sky bias (using r^2/flux as an independent variable).
+
+    Notes:
+
+    - is the psf-fit error distribution reasonable?  perhaps a little
+      on the high side: at 10000 DN, noise is 0.0138, should be
+      0.0107.
+
+    - scatter of ap-fit is surprisingly high.  M_input - M_fit has a
+      median chisq of 2 (mean of 5), while M_input - M_ap has a median
+      chisq of 125 (mean of 380).  This is using a radius of 15.  It
+      cleans up hugely when I go to a radius of 7 and even more so
+      with radius = 4. (In this case, the PSF sigma is 1.5, so 4 is
+      well matched and 7 is only slightly large).
+
+      * make the initial PSF fitting radius and aperture radius
+	  different?  
+      * make the initial PSF fitting radius a function of the measured
+	  PSF sigma?
+
+    - I have been fitting ap-fit mag without weights; the resulting
+      ap-fit errors are highly over-estimated since the ap-fit scatter
+      is too high.  I have added code to carry the weights (error from
+      the fit only at the moment, ie, not the ap error as well).  This
+      is good, but not as important if the radius is chosen well above.
+
+  * a bias in the psphotEnsemble results depending on the sky level
+    but not in the PSF model fits?  turning on/off CONST_PHOT_WT has
+    an equivalent effect, but is quite small.  why is one sensitive to
+    the sky and the other is not?  
+
+  * the error measured by psphotEnsemble was wrong (and inconsistent
+    for a given weighting situation): fixed the def of the error.
+
+  * still trying to understand the corrections being made.  running a
+    few tests:
+
+    keeping PSF_FIT_RADIUS at 4.0
+
+    t1 : no growth correction,    constant weights
+    t2 :    growth correction,    constant weights
+    t3 : no growth correction, no constant weights
+    t4 :    growth correction, no constant weights
+    t5 :    growth correction,    constant weights, constant ap (4)
+    t6 :    growth correction,    constant weights, integer radius
+    t7 :    growth correction,    constant weights, integer radius
+    (for t7, I've fixed the bug that the growth correction include the
+    min radius).
+
+  * psphotGrowthCurve is messed up : the measurement does not correct
+    for the actual object center, and at small radii this is an
+    important source of noise.
+
+2006.06.28
+
+  I am adding a few minor features.  First up is the ability to break
+  the processing at interesting stages.  These will be:
+
+  - PEAKS: after sources are generated, but before moments are
+    measured
+  - MOMENTS: after moments are measured, before the PSF is measured
+  - PSFMODEL: after the psf model is measured, before sources are
+    fitted in bulk
+  - ENSEMBLE: after the linear ensemble fitting
+  - DEFAULT: complete processing
+
+2006.04.22
+
+  I have updated psphot to work with the cycle 11 release of psLib and
+  psModules.  At this point, the IfA group is now working off of the
+  CVS head rather than from a separate IfA branch of psLib and
+  psModules.  The cycle 11 release of psLib needed a few updates to
+  fix minor problems.  The psModules code needed significant work to
+  fix discrepancies.  The new version of psphot has also been leak
+  checked, at least for basic configurations.  This new version of
+  psphot should be used with the new camera configuration system,
+  which can be found in the ipp/config directory.  Here are the
+  necessary tags:
+
+  psphot: psphot_dev_11_0
+  psModules: rel11_ifa_pre0
+  psLib: rel11_ifa_0
+
+2006.04.16
+
+  Examining some of the throughput issues.  the flux measurement speed
+  is strongly dependent on the interation of the function step size:
+
+  dz = 0.01 : 3.24s for 4400 objects
+  dz = 0.1  : 0.44s for 4400 objects
+
+  there is an increased error in the result with the larger step size,
+  but it is smaller than the phot error.  Still, we could do better
+  with a smarter integration function.
+
+  Including the aperture photometry on all sources, but not the
+  pixWeight, the source magnitudes takes 4.8 sec for 4400 objects.
+
+  Adding the pixWeight increases the time to 7.5 sec for 4400 objects
+
+2006.02.12
+
+  Last week, I made a new tag (psphot_dev_08) after finishing the work
+  to clean memory leaks and to modify psphot to work with Paul's
+  ppImage infrastructure.  
+
+  I have added a new background model function which generates a
+  median map based on Robert's suggestion of a) half-step windows and
+  b) linear interpolation.
+
+  Now, I am working to remove the constant sky level concepts and make
+  every relevant test use the signal/noise ratio as the trigger.  This
+  has impacts on the peak detections, the threshold for blends, and
+  the threshold for choosing pixels in the fitting routines.
+
+2005.12.23
+
+  Improvements in psphot as of psphot_dev_07
+
+  - updated to work with psModule release 9.0
+  - moved model test functions into psphot as optional mode
+  - added pmSourceFitSet : simultaneous model group fitting
+  - added bicubic interpolation to tweak up the peak coordinates
+  - added aperture residual analysis after complete object model & subtraction
+  - added 3D (x,y,rflux) aperture residual fitting
+  - added blend vs blob fitting (BlendFit)
+  - added ensemble PSF fitting, with sparse matrix inversion
+  - improved the flagging / marking of model results
+  - better abstraction for setting fit parameters
+  - various low-level fixes
+  - added PSF output option
+  - added break points
+  - added more optional output formats
+  - fixed FITS table output
+
+  Things still to be added to psphot:
+
+  - better initial background model (NxM median grid?)
+  - include local background model in EnsemblePSF Fit (use sparse matrix)
+  - 2nd pass (after object subtraction) to smooth and detect faint objects
+  - re-add background to object-subtracted image
+  - cleanup function names
+  - push more functions into psLib / psModule
+  - fix extended / galaxy models to improve stability
+  - add mode with input PSF
+  - add mode with input fixed sources (PS_SOURCE_FIXED)
+  - cleanup memory leaks
+
+2005.11.25
+
+  I've updated psphot to work with the current psLib v8, though a
+  number of psLib fixes were needed.  These are pushed under
+  psLib:eam_rel8_b2.  The psphot code which works with that version of
+  psLib is tagged psphot_dev_04.
+
+  I'm working on converting psphot to work with the current release of
+  psModules, which basically includes all of the object functions. 
+
+  - change comments are relative to psModules (vs psphot versions)
+
+  psEllipse.c : OK
+  psEllipse.h : OK
+  pmModelGroup.c : OK (missing TGAUSS entry)
+  pmModelGroup.h : OK (adds many comments)
+
+  pmPSFtry.c : a bunch of formatting weirdness (try-> got line breaks
+	       added pmPSFtryMetric_Alt
+	       fixed up usage of psVectorClipFitPolynomial1D  
+	       fixed up usage of psPolynomial..Alloc  
+
+  pmPSFtry.h : OK (Added extensive comments)
+
+  pmPSF.c : fixed up usage of psPolynomial..Alloc
+
+  pmPSF.h : OK (Added extensive comments)
+
+  pmObjects.c : cleaned up some formatting,
+		fixed usage of psImageCountPixelMask
+
+  pmObjects.h : added apMag and fitMag to pmSource
+		dropped PS_MODEL_name defines
+
+  psLibUtils.[ch] should be dropped from psModules
+  psModulesUtils.[ch] should be dropped from psModules
+
+  Makefile.am : dropped psModulesUtils.[ch], psLibUtils.[ch]
+
+  - I have successfully tested psphot with the psModules.v8 code
+    (eam_rel8_b1 tag).  I only needed to make a few modes in the
+    psModules code.  
+
+2005.11.16
+
+  I have made some fixes to make psphot work with pslib rel8_0.  I
+  need to merge psphot with the rel8_0 version of the psMinimize and
+  pmObjects code.  
+
+2005.09.06
+
+  I have built a working version of PSPhot using my own copy of the
+  pmObjects.[ch] files.  I need to minimize the difference between the
+  v0.7.0, v0.5.0, and my own version of pmObjects.c
+
+  additions to psLib based on the new version:
+  - extended the minimization to include parameter limits
+
+  changes v0.5.0 / v0.7.0 / EAM
+  - changed all ps* to pm*
+  - fixed naming for local static functions (eg modelFree not p_psModelFree)
+  - dropped all hard-coded model names (vs v0.7.0)
+  - using current psMemSetDellocator name
+  - added noise image to pmSource
+  - converted old asserts to new asserts (PS_.._CHECK..)
+  - add return from error in pmFindImagePeaks
+  - allow isItInThisRegion to have NULL region (true)
+  - fixed subImages to correspond to region definition (end is
+    exclusive) in LocalSky and SetPixelCircle
+  - dropped old, redundant code in pmSourceLocalSky
+  - fixed pmSourceMoments use of mask (0 is valid, not 1)
+  - added pmSourceMoments validity tests (numPixels, Sum, centroid shift)
+  - added correction for Sxy (XY/Sum - x*y)
+  - added pmSourcePSFClump function (and pmPSFClump structure)
+  - allow missing sources in pmSourcePSFClump (not all peaks yield
+    sources)
+  - moved pmSourceRoughClass metadata lookups out of inner loop
+  - added saturated pixel test using pmImageCountPixelMask
+  - various redefinitions of initial classes
+  - dropped old, redundant code in pmSourceSetPixelsCircle
+  - mask image set to type psU8 (was psF32)
+  - abstract object model functions for pmSourceModelGuess
+  - converted evalModel to a public function psModelEval, modified params
+  - pmSourceFitModel operates on a specified model
+  - pmSourceFitModel had option to apply/ignore PSF information
+  - pmSourceFitModel calculates yErr
+  - pmSourceFitModel calculates the covariance matrix
+  - added paramMask to pmSourceFitModel 
+  - pmSourceFitModel_EAM uses covar to carry in beta and param
+    limits...
+  - pmSourceFitModel requires solution to stay within image
+  - pmSourceFitModel calculates and saves chisq, nIter, nDOF
+  - pmSourceFitModel uses GaussNewtonDelta for frozen params
+  - added mask to pmSourceAdd, pmSourceSub
+  - model abstractions in pmSourceAdd, pmSourceSub
+  - fixed 'center' option in pmSourceAdd/Sub
+
+  - pmPSF params should be F64
+
+  lingering concerns:
+  - Polynomials and related functions are STILL messed up.  this is a
+    major priority!!!
+  - pmSourceFitModel calculates sqrt(var), psMinimize calculates sq(sqrt(var))
+  - does psImageAlloc zero the image?  if so, drop init routine
+    from pmSourcePSFClump
+  - pmSourceRoughClass uses RDNOISE, GAIN to calculate SN: should use
+    the source->noise image? part of pmSourceMoments?
+  - pmSourceMoments_EAM uses macro for checkRegion
+  - pmObjects_EAM.c uses some hardcoded mask values (LocalSky)
+  - why does LocalSky_EAM not need subImageMask->col0 = subImage->col0??
+  - psGetRowVectorFromImage should be in psLib (in psLibUtils)
+  - pmSourceSetPixelsCircle and pmSourceLocalSky probably could use
+    the psImageMaskRegion, etc functions.
+  - pmSourceSetPixelsCircle uses a hard-coded mask value
+  - define a mask registration process??
+  - pmSourceContour is using the model flux, not the image flux
+
+2005.09.05
+
+  I have a working version of PSPhot which handles PSF and galaxy
+  models.  I am tagging the module with an alpha version number. this
+  version compiles with psLib-0.5.0, at least my version of it.  My
+  current goal is to make it work with the current psLib head (nearly
+  0.7.0) with a mimimum of _EAM versions of functions.
+
+  the tag for the working version is dev_01
+
+2005.06.04
+
+  progress on psphot has moved along well.  At this point, the process
+  loads the image, finds the peaks, determines a best PSF model,
+  identifies objects which are consistent with that model, and
+  attempts to fit a galaxy model to the objects which are not
+  consistent with the model. 
+
+  a variety of minor issues remain, as well as some major issues. 
+
+  - use a proper noise image to keep the fits honest after other
+    objects have been subtracted.  
+
+  - define the masks in terms of a single mask image.  this image
+    could be provided initially by the user.   
+
+  - add noise enhancement (couple with mask marking subtraction)
+
+  - 
+
+2005.04.12
+
+- psPeak, psSource, etc: should be pmPeak, pmSource, etc
+- pmCullPeaks: should be pmCullImagePeaks
+
+- does pmSourceMoments need an image argument?
+
+- psLibInit / p_psTimeInit
+
+  we should not have to know about astronomy time concepts (ie, have a
+  timeConfig file) in order to use the time functions for basic work.
+  This means having a PS_TIME_UNIX which just works with the UNIX
+  clock and avoids all initialization issues.
+
+- psImageStats is much too slow for basic median
+  - this function does a complete sort on all pixel values.  we need
+  to use the histogram method (spec a different type of median)
+
+- psImageStats, ROBUST_MEDIAN fails
+- there are errors in psVectorStats for ROBUST MEAN, STDEV (& median?)
+  * this is ill-defined.  revise the ADD: too sensitive to the
+  binning, smoothing scales 
+
+- pmFindImagePeaks is not finding any peaks:
+  typo in interior row section: see bug 359
+  after this fix: PASS
+
+* p_psGetRowVectorFromImage : PASS
+* psGetRowVectorFromImage : PASS 
+- both have about the same time (~15 us +/- 5us) for 2048 pix on alala
+
+* pmFindVectorPeaks : PASS (returned row data in data.U32)
+
+- # include <string.h> : needed in psMetadata.c
+
+* MyListAddPeak / pmPeakAlloc are inconsistent wrt col,row vs x,y
+
+- why does psMetadataItemAllocV allocate a string of length
+  MAX_STRING_LENGTH for the comment, then use strncpy?
+
+  should be:
+
+    // set metadata item comment
+    if (comment == NULL) {
+        // Per SDRS, null isn't allowed, must use "" instead
+        metadataItem->comment = psStringCopy ("");
+    } else {
+        metadataItem->comment = psStringCopy (comment);
+    }
+
+psMetadataAdd -> psMetadataAddItem is all confused:
+ - we end up with two psMetadataItemAlloc calls, when only one should
+   be used
+ - psMetadataItemAlloc was doing the wrong thing if the incoming data
+   was NULL
+ - I think the logic in this block is wrong as well.  
+
+ * I fixed this in psMetadata.c
+
+2005.04.14 : 
+
+ - psImageStats is still much too slow
+
+ - pmObjects : changed to using psArray to carry peaks / sources,
+   rather than psList
+
+ - added function pmPeaksSubset to replace pmCullPeaks
+
+ - pmSourceLocalSky: added min/max fncs to force subimage to stay in
+   image
+
+ - Sx, Sy can go negative: have forced limit to 0,0
+
+ - pmSourceRoughClass: implemented sigmaX, sigmaY search for stellar
+   clump (uses pmComparePeakDescend)
+
+   - the search ignores the 0,0 pixel
+
+   - pmSourceRoughClass uses all detected peaks.  allow an exclusion
+     for peak > max, peak < min?
+
+   - need to push clump stats on the metadata
+
+   - the source classes are assigned so they are mutually exclusive 
+
+   - change PS_SOURCE_OTHER to STAR? 
+
+ - pmSourceSetPixelCircle:
+
+   - name should be pmSourceSetPixelsCircle (geometry could be an
+     option...)
+
+   - I cleaned the defined region to match the convensions of
+     pmSourceLocalSky
+
+   - truncate and force subimage to lie on image
+
+   - CheckRadius2 seems like an inefficient coding
+
+   - valid pixels have mask value of 1 (inverse of other funcs)
+
+ - pmSourceFitModel:
+
+   - num iterations is much too large
+
+   - 
+
+mrq:
+
+ - allocate alpha, beta arrays
+ - mrq2dcof (pars, alpha, beta)
+ - make guess pars', alpha', beta'
+ - mrq2dcof (pars', alpha', beta')
+ - if (chisq < old chisq), keep new guess
+
+
+2005.04.19:
+
+- psMinimizeLMChi2 has some serious problems.  I re-wrote it starting
+  from the mrqmin example in ohana.   after a variety little bugs, it
+  seems to work quite well.  the program 'modeltest.c' runs two
+  different gaussians through psMinimizeLMChi2, and gets the right
+  answer quite quickly.  The first pass at the implementation had a
+  variety of problems.  to avoid any worry about errors in the
+  psMatrix code, I implemented psGaussJordan, basing the code on the
+  elixir gaussj.c code.  this worked fine, and let me find the errors
+  elsewhere in the code. the LUD version now works just the same as
+  the GaussJ version.
+
+
+2005.04.21
+
+- psMinimizeLMChi2 does an excellent job now in most cases.  I am able
+  to run pmSourceFitModel on the PSF stars quite well.  I added in the
+  (nearly) correct errors (actually, just using sqrt(N), limiting to
+  N>=1).  I see one problem object, which is quite faint, and does not
+  converge well: I get very large values for x,y, Sx, Sy, etc.  I need
+  to think about constraining these fits from running way off scale.
+
+  fit:   0.123879 sec for 277 stars (gauss)
+  fit:   0.155940 sec for 277 stars (pgauss) (!)
+  fit:   0.153292 sec for 277 stars
+  (0.5 msec per star)
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/outline.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/outline.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/outline.txt	(revision 21632)
@@ -0,0 +1,21 @@
+
+Here is a summary outline of the steps taken by psphotReadout:
+
+* setup (choose recipe, choose readout, define break-point)
+* create mask and weight images if needed, set mask for analysis region
+* psphotImageMedian : construct a background model image and subtract it
+* psphotFindPeaks   : smooth and find the peak pixels from the smoothed image
+  - no pmSource defined yet, only pmPeak
+* psphotSourceStats : create sources for each peak and measure their moments
+  - pmSource defined here, with pixels covering SKY_OUTER_RADIUS
+  - no pmModel is defined yet.
+* psphotBasicDeblend : identify blended sources by proximity and valley depth
+* psphotRoughClass : source classification guess based on moments
+  - set the source.type,mode.
+  - re-calculate moments for saturated stars
+* psphotChoosePSF : define the psf model
+  - this generates model fits to the identified psf stars
+    (one for each tested model)
+  - these models are not kept (seems like a waste, but later fits must be consistent)
+* psphotGuessModels : set the initial PSF model for each object (even EXTENDED)
+  - creates modelPSF entries
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/output.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/output.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/output.txt	(revision 21632)
@@ -0,0 +1,15 @@
+
+we have several output formats:
+  - TEXT : separate ASCII tables for PSF, EXT, MNT, and NULL
+  - SX : sextractor-style ASCII file
+  - OBJ : dophot-style ASCII file
+  - CMP : old-style elixir format (header + text)
+  - CMF : fits-table
+
+we have several options for organization:
+  - single chip/readout : outroot.ext
+  - split chips : outroot.NN.ext
+  - mef chips : outroot.ext (extensions with names)
+
+  * only CMF will work with MEF model
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/psfmodel.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/psfmodel.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/psfmodel.txt	(revision 21632)
@@ -0,0 +1,22 @@
+
+2006.10.27
+
+  I have been working to fix the PSF modeling in psphot.  The PSF
+  model consists of a flux model for an individual object using an
+  analytical model with a number of parameters.  For a collection of
+  PSF objects, the variation of the parameters as a function of
+  position are then themselves fitted with a model.  The PSF model for
+  a single object consists of a radial profile with a functional form
+  f(z) where a given value of z defines an elliptical coutour of the
+  form z = \frac{x^2}{2\sigma_x^2} + \frac{y^2}{2\sigma_y^2} +
+  \sigma_{xy}xy.
+
+
+
+  The term \sigma_{xy} is difficult to model as a simple function of x
+  and y (eg, a low-order polynomial).  
+
+  A better model can be constructed for
+  \frac{\sigma_{xy}}{(\sigma_x^{-2} + \sigma_y^{-2})^2}, which varies
+  like a^2 \sin 2\theta 
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/psphot.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/psphot.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/psphot.txt	(revision 21632)
@@ -0,0 +1,160 @@
+
+TBD priorities:
+
+- test psImageSmooth / remove psImageSmooth_EAM
+- unify pmSourceLocalSky / pmSourceLocalSky_EAM
+- unify pmSourceMoments_EAM / pmSourceMoments_EAM
+- merge with image data hierarchy stuff
+- better name for psPSF_Test
+
+Defined APIs:
+
+top-level psphot functions : these do not need to be in psModules
+  psMetadata  *psphotArguments ();
+  psImageData *psphotSetup ();
+  psStats     *psphotImageStats ();
+  psArray     *pmPeaksSigmaLimit ();
+  psArray     *psphotSourceStats ();
+  pmPSF       *psphotChoosePSF ();
+  bool         psphotApplyPSF ();
+  bool         psphotFitGalaxies ();
+  void         psphotOutput ();
+
+psphot-specific functions
+  bool         psphotMarkPSF ();
+  bool         psphotSubtractPSF ();
+  int          psphotSortBySN ();
+  int          psphotSaveImage ();
+
+** add the following entries to the psLibSDRS
+
+psLib extra utilities
+  bool         psTimerStart ();		- already in SDRS
+  void         psTimerFree (); 		- already in SDRS
+  bool         psTimerClear ();		- add to psLibSDRS
+  psF64        psTimerMark (); 		- already in SDRS
+  psS32        psLogArguments ();	- now: psArgumentVerbosity in SDRS
+  psS32        psTraceArguments ();	- now: psArgumentVerbosity in SDRS
+  int          psArgumentGet ();	- already in SDRS
+  int          psArgumentRemove ();	- already in SDRS
+  psVector    *psVectorCreate ();	- add to psLibSDRS
+  int          psImageCountPixelMask ();- add to psLibSDRS
+  psVector    *psGetRowVectorFromImage(); - add to psLibSDRS?
+  bool         p_psVectorPrintRow ();     - add to psLibSDRS?
+
+// basic image functions
+  bool         psImageInit ();		- already in SDRS
+  void         psImageSmooth_EAM ();    - test psLib version and drop
+
+// psLine functions
+  psLine      *psLineAlloc ();		- add to psLibSDRS
+  bool         psLineInit (); 		- add to psLibSDRS
+  bool         psLineAdd ();  		- add to psLibSDRS
+
+** compare with current implementation / update psLib
+   we need to define a mechanism to carry the beta values and the
+   parameter limits
+
+// minimize 
+  psBool       p_psMinLM_GuessABP_EAM ();
+  psBool       psMinimizeLMChi2_EAM();
+  psF64        p_psMinLM_dLinear ();  -- not included in pslib.h files
+
+** already in psLib : some need to be cleaned up by MHPCC
+
+// polynomial functions
+  psF64           Polynomial2DEval();
+  psImage        *psBuildSums2D();
+  psPolynomial2D *VectorFitPolynomial2DOrd_EAM();
+  psPolynomial2D *Polynomial2DAlloc();
+  void            psPolynomial2DDump ();
+  psPolynomial2D *RobustFit2D_nomask();
+  psPolynomial2D *RobustFit2D();
+  psVector       *Polynomial2DEvalVector();
+
+  psVector       *psBuildSums1D();
+  void            psPolynomial1DDump ();
+  psF64           Polynomial1DEval_EAM();
+  psVector       *Polynomial1DEvalVector_EAM();
+  psPolynomial1D *Polynomial1DAlloc();
+  psPolynomial1D *VectorFitPolynomial1DOrd_EAM();
+
+** make sure these are all in the psModules SDRS:
+
+pmObjects.h:
+  pmMoments           *pmMomentsAlloc();
+  pmModel             *pmModelAlloc();
+  pmSource            *pmSourceAlloc();
+  psVector            *pmFindVectorPeaks()
+  psArray             *pmFindImagePeaks()
+  psList              *pmCullPeaks()
+  pmSource            *pmSourceLocalSky()
+  bool                 pmSourceMoments()
+  pmPSFClump           pmSourcePSFClump()
+  bool                 pmSourceRoughClass()
+  bool                 pmSourceSetPixelsCircle()
+  pmModel             *pmSourceModelGuess()
+  psArray             *pmSourceContour()
+  bool                 pmSourceFitModel()
+  bool                 pmSourceAddModel()
+  bool                 pmSourceSubModel()
+  int                  pmModelParameterCount ();
+  char                *pmModelGetType ();
+  pmModelType          pmModelSetType ();
+  pmModelFunc          pmModelFunc_GetFunction ();
+  pmModelFlux          pmModelFlux_GetFunction ();
+  pmModelRadius        pmModelRadius_GetFunction ();
+  pmModelLimits        pmModelLimits_GetFunction ();
+  pmModelGuessFunc     pmModelGuessFunc_GetFunction ();
+  pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction ();
+  pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction ();
+
+model function abstractions:
+  typedef psMinimizeLMChi2Func pmModelFunc;
+  typedef psF64 (*pmModelFlux)(const psVector *params);
+  typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+  typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+  typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+  typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+  typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+conversions between elliptical shape representations
+  EllipseAxes  EllipseMomentsToAxes ();
+  EllipseShape EllipseAxesToShape ();
+  EllipseAxes  EllipseShapeToAxes ();
+
+output functions
+  bool         pmSourcesWriteText ();  	-- be careful about psImageData...
+  bool         pmSourcesWriteOBJ ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMP ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMF ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteSX ();    	-- be careful about psImageData...
+  int          pmSourcesDophotType ();
+  bool         pmPeaksWriteText ();
+  bool         pmMomentsWriteText ();
+  bool         pmModelWritePSFs ();
+  bool         pmModelWriteFLTs ();
+  bool         pmModelWriteNULLs ();
+
+// psf utilities
+  pmPSF       *pmPSFAlloc ();
+  pmPSF_Test  *pmPSF_TestAlloc ();
+  pmPSF_Test  *pmPSF_TestModel ();
+  bool         pmPSFFromModels ();
+  pmModel     *pmModelFromPSF ();
+  bool         pmSourcePhotometry ();
+  bool         pmPSFMetricModel ();
+
+// psModule extra utilities
+  bool         pmSourceFitModel_EAM();  -- requires updated version of psMinimize...
+  bool         pmSourceLocalSky_EAM (); -- compare with pmSourceLocalSky & choose
+  bool         pmSourceMoments_EAM();   -- compare with pmSourceMoments & choose
+  bool         pmSourceDefinePixels();  -- be careful about psImageData...
+  bool         pmModelFitStatus ();
+  int          pmSourceDophotType ();
+  psF32        pmConfigLookupF32 ();	-- merge with Paul's image container hierarchy
+
+** this needs to be confronted with the phase2 / image container hierarchy
+
+// psImageData functions
+  psImageData *psImageDataAlloc ();
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/regions.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/regions.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/regions.txt	(revision 21632)
@@ -0,0 +1,159 @@
+
+I am having some trouble with functions that refer to both subimages
+and regions.  there are inconsistencies between the coordinate
+conventions.  I am going to go through the list of image functions are
+decide if we can choose either 1) external coordinates always refer to
+parent coordinates or 2) something else!
+
+bool psImageSet(const psImage *image, int x, int y, double complex value);
+double complex psImageGet(const psImage *image, int x, int y);
+ - sdrs does not specify
+ - code uses subimage
+
+psRegion psRegionForImage(psImage *image, psRegion in);
+ - sdrs says subimage
+ - code uses subimage
+
+psImage *psFitsReadImage(psImage *out, const psFits *fits, psRegion region, int z);
+ - region here specifies a subimage of the disk image
+ 
+bool psFitsUpdateImage(psFits *fits, const psImage *input, int x0, int y0, int z);
+ - x0,y0 here refer to the disk image coordinates
+
+bool psFitsWriteImage(psFits *fits, psMetadata *header, const psImage
+*input, int depth, const char *extname);
+ - note that the output disk image is truncated to the subimage
+
+bool psFitsInsertImage(psFits *fits, psMetadata *header, const psImage *input, int depth,
+                       const char *extname, bool after);
+ - same as above
+
+psImage *psImageSubset(psImage *image, psRegion region);
+ - code uses region as coordinates in parent image
+ - sdrs does not specify
+ - XXX I think the range tests for region fail if the input is a
+   subimage
+
+psImage *psImageCopy(psImage *output, const psImage *input, psElemType type);
+ - maintains the col0,row0 values
+
+psVector *psImageRow(psVector *out, const psImage *input, int row);
+psVector *psImageCol(psVector *out, const psImage *input, int column);
+ - code uses subimage coordinates for row and column
+
+psVector *psImageSlice()
+psVector* psImageCut()
+ - code uses subimage coordinates (no correction)
+ - sdrs does not specify
+
+psImage *psImageRebin()
+ - mask and input image must currently agree
+
+psImage *psImageTransform()
+ - sdrs is unclear: I think the region defines the output image such
+ that the pixel in the input image *parent* coordinates which
+ corresponds to region.x0,y0 in turn corresponds to the output image
+ 0,0 pixel; the output image always has col0,row0 = 0,0
+
+long psImageCountPixelMask (psImage *mask, psRegion region, psMaskType value);
+ - sdrs says the region corresponds to subimage coordinates
+
+psPolynomial2D *psImageFitPolynomial(psPolynomial2D *coeffs, const psImage *input);
+psImage *psImageEvalPolynomial(psImage *input, const psPolynomial2D *coeffs);
+ - sdrs does not specify
+ - polynomial coordinates should refer to the *parent* image
+
+double complex psImagePixelInterpolate(input, x, y, ...)
+ - sdrs does not specify
+
+int psImageOverlaySection(psImage *image, const psImage *overlay, )
+ - sdrs does not specify
+
+psImage *psPixelsToMask()
+psPixels *psPixelsFromMask()
+ - sdrs does not specify
+
+void psImageMaskRegion()
+void psImageKeepRegion()
+ - sdrs does not specify
+
+void psImageMaskCircle()
+void psImageKeepCircle()
+ - sdrs does not specify
+
+pmReadout *pmSubtractSky(pmReadout *in, psPolynomial2D *poly, psImage *mask, psU8 maskVal, 
+                         int binFactor, psStats *stats, float clipSD);
+ - choice of polynomial coordinates
+
+stats *pmFringeStats (psArray *fringePoints, psImage *image, psMetadata *config);
+ - unspecified
+
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row);
+ - sdrs says col,row are in *subimage* coords
+
+psArray *pmFindImagePeaks(const psImage *image, float threshold);
+ - does not specify coordinate system
+ - code uses subimage?
+
+bool pmSourceDefinePixels()
+bool pmSourceRedefinePixels()
+ - sdrs does not specify very clearly
+
+
+
+---------
+
+functions which are not affected
+psFitsReadImage()
+psFitsUpdateImage()
+psFitsWriteImage()
+psFitsInsertImage()
+psImageCopy()  -- maintains col0,row0
+psImageRebin() -- mask and image must match
+
+functions which currently use parent coordinates (CORRECT)
+psImageSubset()  -- check on range tests
+psImageMaskRegion()
+psImageKeepRegion()
+psImageMaskCircle()
+psImageKeepCircle()
+pmSourceMoments -- (OK if input peaks are in parent coords)
+
+functions which currently use subimage coordinates (FIX)
+psImageSet()
+psImageGet()
+psRegionForImage() *fixed*
+psImageRow()
+psImageCol()
+psImageSlice()
+psImageCut()
+psImageCountPixelMask ();
+pmModelEval()
+pmFindImagePeaks() *fixed*
+pmSourceDefinePixels() *fixed by psRegionForImage fix*
+pmSourceRedefinePixels() *fixed by psRegionForImage fix*
+
+functions which are unclear (CHECK)
+psImageTransform()
+psImageFitPolynomial()
+psImageEvalPolynomial()
+psImagePixelInterpolate()
+psPixelsToMask()
+pmSubtractSky()
+pmFringeStats()
+
+
+***** one ambiguity: if we have a subimage, but we do not have the
+      parent image data (subimage does not carry dimensions of parent
+      array), then a 0 or negative upper-edge of a region cannot be
+      defined relative to the parent. we can specify it relative to
+      the subimage.  so, for example, given a subimage [32,100:32,100]
+      of a much larger image, then psRegionForImage with 0,0,0,0 as
+      input would return a region with values 32,100:32,100...
+
+
+
+
+
+*** things I've modified
+    psRegionForImage expects parent (not subimage) coordinates
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/speed.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/speed.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/speed.txt	(revision 21632)
@@ -0,0 +1,129 @@
+
+psphot -g
+psModule -g
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.983568 sec
+    System random seed value used  seed = 25904F7C7454AE25 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063085 sec
+    System random seed value used  seed = 411E5A0600A49AF3 hex
+    fit background model: 0.580152 sec
+    smooth: 1.842415 sec
+    threshold: 83.100540 DN
+    4356 peaks: 0.571587 sec
+    4230 moments: 3.965099 sec
+    SN range: 3.355829 - 1043.401001
+    identified 107 blended objects (0.121737 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.682850 sec for 300 sources
+    fit psf:   1.704141 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717999
+    select psf model: 5.402742 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.991345 (4230 objects)
+    built matrix: 1.472869 (27998 elements)
+    measure ensemble of PSFs: 4.334556
+    fit PSF models: 12.205738 sec for 4230 objects
+    replace unfitted models: 0.021473 sec (4230 objects)
+    measure aperture residuals : 0.451886 sec
+    measure full-frame aperture residual: 0.454032 sec
+    aperture residual: -0.002842 +/- 0.002729 : 0.166072 bias
+    wrote output: 4.648821 sec
+    complete psphot run: 35.334080 sec
+
+
+psphot -g
+psModule -O2
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 1.001502 sec
+    System random seed value used  seed = 5F43A63F532D4B3F hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063169 sec
+    System random seed value used  seed = F5F5FF99A6578F61 hex
+    fit background model: 0.530384 sec
+    smooth: 1.856414 sec
+    threshold: 83.100540 DN
+    4365 peaks: 0.237292 sec
+    4239 moments: 4.087611 sec
+    SN range: 3.356469 - 1043.400879
+    identified 107 blended objects (0.123372 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.741444 sec for 300 sources
+    fit psf:   1.676478 sec for 300 sources
+    fit stats: 0.000883 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717985
+    select psf model: 5.458156 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.791613 (4239 objects)
+    built matrix: 1.267909 (28101 elements)
+    measure ensemble of PSFs: 3.551364
+    fit PSF models: 11.240197 sec for 4239 objects
+    replace unfitted models: 0.018051 sec (4239 objects)
+    measure aperture residuals : 0.411856 sec
+    measure full-frame aperture residual: 0.413843 sec
+    aperture residual: -0.002831 +/- 0.002717 : 0.164772 bias
+    wrote output: 4.355429 sec
+    complete psphot run: 33.126283 sec
+
+after reducing indirection in pmSourceMoments:
+    4268 moments: 3.762452 sec
+
+psphot -O2
+psModule -O2
+psLib -O2
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.484324 sec
+    System random seed value used  seed = 980050732A392EC7 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.058759 sec
+    System random seed value used  seed = 44E1A6EE77B0E9CE hex
+    fit background model: 0.395125 sec
+    smooth: 1.861776 sec
+    threshold: 83.100540 DN
+    4351 peaks: 0.234874 sec
+    4264 moments: 3.719435 sec
+    SN range: 2.023345 - 1043.401123
+    identified 124 blended objects (0.115230 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.525501 sec for 300 sources
+    fit psf:   1.595031 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717976
+    select psf model: 5.160442 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.760805 (4264 objects)
+    built matrix: 0.908674 (23703 elements)
+    measure ensemble of PSFs: 3.115425
+    fit PSF models: 10.162397 sec for 4264 objects
+    replace unfitted models: 0.017491 sec (4264 objects)
+    measure aperture residuals : 0.408242 sec
+    measure full-frame aperture residual: 0.410207 sec
+    aperture residual: -0.002908 +/- 0.002734 : 0.170692 bias
+    wrote output: 4.225560 sec
+    complete psphot run: 30.149281 sec
+
+
+    fit PSF models: 10.162397 sec for 4264 objects
+    select psf model: 5.160442 sec
+    wrote output: 4.225560 sec
+    4264 moments: 3.719435 sec
+    measure ensemble of PSFs: 3.115425 sec
+
+    other : 3.5 sec
+
+    fixed per objects : 4 msec / object (17 of 30 sec)
+
+
+** still can re-compile with -DPS_NO_TRACE
+** ASSERTS cannot be compiled out at the moment!
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/doc/versions.txt
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/doc/versions.txt	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/doc/versions.txt	(revision 21632)
@@ -0,0 +1,49 @@
+
+2006.02.02
+  I'm tagging psphot after I updated it to work with the readouts
+  using the structures being used for ppImage.  In this release, I
+  have also moved to autoconf for psphot, and the psphotReadout
+  portion is being defined as a library against which the other
+  elements are linked.  It should be possible to use this library with
+  ppImage, but I have not yet tested that.  
+
+  link psphot_dev_08 against psLib:eam_rel9_b2 and
+  psModule:eam_rel9_b2.
+
+2006.01.18
+  the tag below (psphot_dev_07) was moved to a later release.  It
+  should be used with psLib:eam_rel9_b1 from 2006/1/18 and
+  psModule:eam_rel9_p0.  
+
+2005.12.23
+  psphot_dev_07 should be compiled against psLib:eam_rel9_b1 and
+  psModule:eam_rel9_b1.  This version now handles the ensemble PSF
+  fitting, detection of blended sources, fitting of 'blends' (groups
+  of PSFs with distinct be 'blended' peaks) and 'blobs' (unresolved
+  extended objects, with a test for double PSF vs extended source
+  model.
+
+2005.11.25
+
+  psphot_dev_05 should be compiled against psLib tag eam_rel8_b2 and
+  psModules tag eam_rel8_b1.  this version removes all _EAM versions
+  of psLib and psModules code.  It now uses the psModules version of
+  the object code.
+
+  psphot_dev_04 should be compiled against psLib tag eam_rel8_b2 this
+  version removes all _EAM versions of psLib code, so that it is now
+  consistent with psLib.v8 (fixes are pushed into the eam_rel8_b2
+  branch).
+
+2005.11.16
+
+  psphot_dev_03 should be compiled against psLib tag eam_rel8_b1, a
+  branch starting from psLib rel8_0.  I needed to make some minor
+  fixes in psLib to work successfully with psLib rel8_0.
+
+  psphot_dev_02 was getting segfaults from a bug added into psLib in
+  rel8_0 in p_psVectorClippedStats.  I also fixed some errors arising
+  from changing types (argument to psMetadataParseConfig).
+
+
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/psphot.pc.in
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/psphot.pc.in	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/psphot.pc.in	(revision 21632)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libpsphot
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lpsphot
+Libs.private: @PSPHOT_LIBS@
+Cflags: -I${includedir} @PSPHOT_CFLAGS@
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/.cvsignore
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/.cvsignore	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/.cvsignore	(revision 21632)
@@ -0,0 +1,18 @@
+*.o
+*.lo
+.libs
+.deps
+Makefile
+Makefile.in
+libpsphot.la
+psphot
+psphotTest
+psphot.loT
+psphotErrorCodes.h
+psphotErrorCodes.c
+config.h
+config.h.in
+stamp-h1
+
+polyfitTest
+growthTest
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/Makefile.am
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/Makefile.am	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/Makefile.am	(revision 21632)
@@ -0,0 +1,74 @@
+
+lib_LTLIBRARIES = libpsphot.la
+libpsphot_la_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+
+bin_PROGRAMS = psphot
+
+psphot_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+psphot_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSPHOT_LIBS)
+psphot_LDADD = libpsphot.la
+psphot_SOURCES = psphot.c		
+
+libpsphot_la_SOURCES = \
+	psphotErrorCodes.c	\
+	psphotVersion.c		\
+	psphotModelGroupInit.c	\
+	psphotArguments.c	\
+	psphotParseCamera.c	\
+	psphotImageLoop.c	\
+	psphotMaskReadout.c	\
+	psphotReadout.c		\
+	psphotImageMedian.c	\
+	psphotFindPeaks.c	\
+	psphotSourceStats.c	\
+	psphotRoughClass.c	\
+	psphotBasicDeblend.c	\
+	psphotChoosePSF.c	\
+	psphotGuessModels.c     \
+	psphotEnsemblePSF.c	\
+	psphotBlendFit.c	\
+	psphotReplaceUnfit.c	\
+	psphotApResid.c		\
+	psphotMagnitudes.c	\
+	psphotSkyReplace.c	\
+	psphotEvalPSF.c		\
+	psphotEvalFLT.c		\
+	psphotSourceFits.c	\
+	psphotRadiusChecks.c	\
+	psphotSortBySN.c	\
+	psphotOutput.c		\
+	psphotGrowthCurve.c	\
+	psphotFakeSources.c	\
+	psphotModelTest.c	\
+	psphotFitSet.c		\
+	psphotWeightBias.c	\
+	psphotSourceFreePixels.c \
+	psphotSummaryPlots.c     \
+	psphotTestPSF.c          \
+	psphotMergeSources.c	 \
+	psphotReadoutCleanup.c	 \
+	psphotCleanup.c	   	
+
+include_HEADERS = \
+	psphot.h \
+	psphotErrorCodes.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+# Error codes.
+BUILT_SOURCES = psphotErrorCodes.h psphotErrorCodes.c
+CLEANFILES = psphotErrorCodes.h psphotErrorCodes.c
+EXTRA_DIST = psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h.in \
+	models/pmModel_STRAIL.c \
+	models/pmModel_TEST1.c
+
+psphotErrorCodes.h : psphotErrorCodes.dat psphotErrorCodes.h.in
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.h
+
+psphotErrorCodes.c : psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.c
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/models/pmModel_STRAIL.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/models/pmModel_STRAIL.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/models/pmModel_STRAIL.c	(revision 21632)
@@ -0,0 +1,568 @@
+ 
+/****************************************************************************** 
+    params->data.F32[0] = So; 
+    params->data.F32[1] = Zo; 
+    params->data.F32[2] = Xo; 
+    params->data.F32[3] = Yo; 
+    params->data.F32[4] = 1 / SigmaX; 
+    params->data.F32[5] = 1 / SigmaY; 
+    params->data.F32[6] = Sxy; 
+    params->data.F32[7] = length; 
+    params->data.F32[8] = theta; 
+*****************************************************************************/ 
+ 
+# define PM_MODEL_FUNC       pmModelFunc_STRAIL
+# define PM_MODEL_FLUX       pmModelFlux_STRAIL
+# define PM_MODEL_GUESS      pmModelGuess_STRAIL
+# define PM_MODEL_LIMITS     pmModelLimits_STRAIL
+# define PM_MODEL_RADIUS     pmModelRadius_STRAIL
+# define PM_MODEL_FROM_PSF   pmModelFromPSF_STRAIL
+# define PM_MODEL_FIT_STATUS pmModelFitStatus_STRAIL
+
+psF32 PM_MODEL_FUNC(psVector *deriv, 
+			 const psVector *params, 
+			 const psVector *x) 
+{ 
+    psF32 *PAR = params->data.F32; 
+ 
+    psF32 trailLength = PAR[7]; 
+    psF32 theta = PAR[8]; 
+ 
+    psF32 x0 = PAR[2];  //streak center 
+    psF32 y0 = PAR[3];  //streak center 
+ 
+    //S values (1/sigma for x and y case, sigma for xy case) 
+    psF32 sx = PAR[4];   
+    psF32 sy = PAR[5];   
+    psF32 sxy = PAR[6];  
+ 
+    psF32 sinT=sin(theta); 
+    psF32 cosT=cos(theta); 
+    psF32 sin2T=sin(2.0*theta); 
+    psF32 cos2T=cos(2.0*theta); 
+ 
+    //    printf("Trying object at %4.1f,%4.1f with length %3.1f and angle %1.3f\r", x0, y0, length, theta); 
+ 
+    //current location relative to trail center 
+    psF32 X  = x->data.F32[0] - x0; 
+    psF32 Y  = x->data.F32[1] - y0; 
+ 
+    //x' and y' location (trail-orienter coords) 
+    psF32 xs = X*cosT + Y*sinT; 
+    psF32 ys = -1.0*X*sinT + Y*cosT; 
+ 
+    //initialize variables to be changed below 
+    psF32 x1 = 0; 
+    psF32 y1 = 0; 
+    psF32 px = 0; 
+    psF32 py = 0; 
+    psF32 z  = 0; 
+    psF32 zx = 0; 
+    psF32 t  = 0; 
+    psF32 tx = 0; 
+    psF32 r  = 0; 
+    psF32 rx = 0; 
+    psF32 f  = 0; 
+ 
+    psF32 sxrot = 0; 
+    psF32 syrot = 0; 
+    psF32 sxyrot = 0; 
+    psF32 dsxrot = 0; 
+    psF32 dsyrot = 0; 
+    psF32 dsxyrot = 0; 
+ 
+    //    psF32 Rx = 0; 
+    //    psF32 Ry = 0; 
+    //    psF32 Rxy = 0; 
+ 
+ 
+    //calculate new S values (1/sigma) for rotated frame 
+    psF32 sxrotsq = PS_SQR(cosT*sx) + PS_SQR(sinT*sy) + cosT*sinT*sxy; 
+    psF32 syrotsq = PS_SQR(cosT*sy) + PS_SQR(sinT*sx) - cosT*sinT*sxy; 
+ 
+    //    psF32 testtwo=10.1; 
+    //    psF32 testone=fabsf(testtwo); 
+    //    fprintf (stderr, "Test: %f is the absolute value of %f?\n",testone,testtwo); 
+    if (sxrotsq<0) { 
+      sxrot = sqrt(-(sxrotsq)); 
+      syrot = sqrt(syrotsq); 
+      fprintf (stderr, "error in sxrotsq: Neg,  sxrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",sxrotsq,sx,sy,sxy,theta); 
+    } else if (syrotsq<0) { 
+      sxrot = sqrt(sxrotsq); 
+      syrot = sqrt(-(syrotsq)); 
+      fprintf (stderr, "error in syrotsq: Neg,  syrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",syrotsq,sx,sy,sxy,theta); 
+    } else if (sxrotsq==0){ 
+      sxrot = 0.01; 
+      syrot = sqrt(syrotsq); 
+      fprintf (stderr, "error in sxrotsq: Zero,  sxrotsq=%f \n",sxrotsq); 
+    } else if (syrotsq==0) { 
+      syrot = 0.01; 
+      sxrot = sqrt(sxrotsq); 
+      fprintf (stderr, "error in syrotsq: Zero,  syrotsq=%f \n",syrotsq); 
+      //      return(0); 
+    }else { 
+      sxrot = sqrt(sxrotsq); 
+      syrot = sqrt(syrotsq); 
+    } 
+ 
+    sxyrot = sxy*cos2T + (PS_SQR(sy) - PS_SQR(sx))*sin2T; 
+ 
+      if (isnan(sxrot)) { 
+	fprintf (stderr, "error in sxrot  syrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",syrot,sx,sy,sxy,cosT,sinT); 
+      } else if (isnan(syrot)) { 
+	fprintf (stderr, "error in syrot  sxrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",sxrot,sx,sy,sxy,cosT,sinT); 
+      }  
+ 
+    //calculate length of pipe (not of trail motion) 
+    psF32 length = trailLength - 2.0*2.0/sxrot; 
+ 
+ 
+    if (xs < length/(-2.0)) { 
+      x1 = (xs+length/2.0)*cosT - ys*sinT; //Endpoint1 
+      y1 = (xs+length/2.0)*sinT + ys*cosT; //Endpoint1 
+      //1.6 factor comes from by-eye testing of fits, sqrt from the later squaring of it 
+      //1.6 ~= phi (golden mean)...coincidence? 
+      px = sxrot*x1/sqrt(1.6); 
+      py = syrot*y1; 
+ 
+      //first find out what the falloff in the x direction is... 
+      zx  = 0.5*PS_SQR(px); 
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0; 
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0); 
+ 
+      //...and now in the y direction 
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1; 
+      t  = 1 + z + z*z/2.0; 
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */ 
+      f  = PAR[1]*rx*r + PAR[0]; 
+ 
+    } else if (xs > length/2.0){ 
+      x1 = (xs-length/2.0)*cosT - ys*sinT; //Endpoint2 
+      y1 = (xs-length/2.0)*sinT + ys*cosT; //Endpoint2 
+      px = sxrot*x1/sqrt(1.6); 
+      py = syrot*y1; 
+      zx  = 0.5*PS_SQR(px); 
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0; 
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0); 
+ 
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1; 
+      t  = 1 + z + z*z/2.0; 
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */ 
+      f  = PAR[1]*rx*r + PAR[0]; 
+ 
+      if (isnan(r)) { 
+	fprintf (stderr, "error in +r  t=%f z=%f\n",t,z); 
+      }  
+ 
+    } else { 
+      x1 = -ys*sinT; 
+      y1 = ys*cosT;  
+      px = sx*x1; 
+      py = sy*y1; 
+      z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + sxy*x1*y1; 
+      t  = 1 + z + z*z/2.0; 
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */ 
+      rx = 1.0;  //so that dF/dF0 can be generalized 
+      f  = PAR[1]*r + PAR[0]; 
+ 
+      if (isnan(r)) { 
+	fprintf (stderr, "error in midr  t=%f z=%f\n",t,z); 
+      }  
+    } 
+ 
+     
+    //ok...so this is df/dPAR[X] 
+    if (deriv != NULL) { 
+        psF32 q = 1; 
+	//stable 
+        deriv->data.F32[0] = +1.0; 
+        deriv->data.F32[1] = +r * rx; 
+ 
+	  if (isnan(deriv->data.F32[0])) { 
+	    fprintf (stderr, "error in deriv0\n"); 
+	  } else if (isnan(deriv->data.F32[1])) { 
+	    fprintf (stderr, "error in deriv1 r=%f rx=%f\n",r,rx); 
+	  }  
+ 
+	dsxrot = 2.0*PS_SQR(sy)*sinT*cosT - 2.0*PS_SQR(sx)*sinT*cosT + sxy*(PS_SQR(cosT)-PS_SQR(sinT)); 
+	dsyrot = 2.0*PS_SQR(sx)*sinT*cosT - 2.0*PS_SQR(sy)*sinT*cosT - sxy*(PS_SQR(cosT)-PS_SQR(sinT)); 
+	dsxyrot = 2.0*cos2T*(PS_SQR(sy)-PS_SQR(sx)) - 2.0*sxy*sin2T; 
+ 
+	  if (isnan(dsxrot)) { 
+	    fprintf (stderr, "error in dsxrot\n"); 
+	  } else if (isnan(dsyrot)) { 
+	    fprintf (stderr, "error in dsyrot\n"); 
+	  } else if (isnan(dsxyrot)) { 
+	    fprintf (stderr, "error in dsxyrot\n"); 
+	  }  
+	 
+	//initialize variables definied in the if statements 
+	//	psF32 XXX=0; 
+	//	psF32 YYY=0; 
+ 
+	   
+	// variable over piecewise func 
+	// change the endcaps to be 4th order gaussians with sxrot_fit=1.6*sxrot 
+	// y' direction can ge adequately modelled by a 3rd order gaussian with syrot 
+	if (xs < length/(-2.0)) { 
+	   
+	  q=PAR[1]*r*rx; 
+	  deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot); 
+	  deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot); 
+	  deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT+2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T+y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot))); 
+	  deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)+2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T+y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot))); 
+	  deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1+2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1+2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T+y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)+x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot))); 
+	  deriv->data.F32[7] = -q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT)); 
+	  deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)+y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)+x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT))); 
+ 
+	  /*	  if (isnan(deriv->data.F32[2])) { 
+	    fprintf (stderr, "error in deriv2\n"); 
+	  } else if (isnan(deriv->data.F32[3])) { 
+	    fprintf (stderr, "error in deriv3\n"); 
+	  } else if (isnan(deriv->data.F32[4])) { 
+	    fprintf (stderr, "error in deriv4\n"); 
+	  } else if (isnan(deriv->data.F32[5])) { 
+	    fprintf (stderr, "error in deriv5\n"); 
+	  } else if (isnan(deriv->data.F32[6])) { 
+	    fprintf (stderr, "error in deriv6\n"); 
+	  } else if (isnan(deriv->data.F32[7])) { 
+	    fprintf (stderr, "error in deriv7\n"); 
+	  } else if (isnan(deriv->data.F32[8])) { 
+	    fprintf (stderr, "error in deriv8\n"); 
+	  } 
+	  */ 
+	     
+ 
+ 
+	} else if (xs > length/2.0){ 
+ 
+	  q=PAR[1]*r*rx; 
+	  deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot); 
+	  deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot); 
+	  deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT-2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T-y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot))); 
+	  deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)-2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T-y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot))); 
+	  deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1-2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1-2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T-y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)-x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot))); 
+	  deriv->data.F32[7] = q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT)); 
+	  deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)-y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)-x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT))); 
+ 
+ 
+	  /*	  if (isnan(deriv->data.F32[2])) { 
+	    fprintf (stderr, "error in deriv2\n"); 
+	  } else if (isnan(deriv->data.F32[3])) { 
+	    fprintf (stderr, "error in deriv3\n"); 
+	  } else if (isnan(deriv->data.F32[4])) { 
+	    fprintf (stderr, "error in deriv4\n"); 
+	  } else if (isnan(deriv->data.F32[5])) { 
+	    fprintf (stderr, "error in deriv5\n"); 
+	  } else if (isnan(deriv->data.F32[6])) { 
+	    fprintf (stderr, "error in deriv6\n"); 
+	  } else if (isnan(deriv->data.F32[7])) { 
+	    fprintf (stderr, "error in deriv7\n"); 
+	  } else if (isnan(deriv->data.F32[8])) { 
+	    fprintf (stderr, "error in deriv8\n"); 
+	  } 
+	  */ 
+ 
+	} else { 
+	  // this does not change from before, as the y' falloff can be modelled by the standard 3rd order gaussian  
+	  // note difference from a pure gaussian: q = PAR[1]*r 
+	  q = PAR[1]*r*r*t; 
+	  deriv->data.F32[2] = q*(PS_SQR(sx*sinT)*x1 - PS_SQR(sy)*y1*cosT*sinT - sxy*x1*sinT*cosT + sxy*y1*PS_SQR(sinT)); 
+	  deriv->data.F32[3] = q*(-1*PS_SQR(sx)*x1*sinT*cosT + PS_SQR(sy*cosT)*y1 + sxy*x1*PS_SQR(cosT) - sxy*y1*sinT*cosT); 
+	  deriv->data.F32[4] = -q*sx*PS_SQR(x1); 
+	  deriv->data.F32[5] = -q*sy*PS_SQR(y1); 
+	  deriv->data.F32[6] = -q*x1*y1; 
+	  deriv->data.F32[7] = 0; 
+	  deriv->data.F32[8] = -q*( PS_SQR(sx)*x1*(xs*sinT - ys*cosT) + PS_SQR(sy)*y1*(xs*cosT - ys*sinT) + sxy*x1*(xs*cosT - ys*sinT) + sxy*y1*(xs*sinT - ys*cosT) ); 
+ 
+	  if (isnan(deriv->data.F32[2])) { 
+	    fprintf (stderr, "error in deriv2\n"); 
+	  } else if (isnan(deriv->data.F32[3])) { 
+	    fprintf (stderr, "error in deriv3\n"); 
+	  } else if (isnan(deriv->data.F32[4])) { 
+	    fprintf (stderr, "error in deriv4\n"); 
+	  } else if (isnan(deriv->data.F32[5])) { 
+	    fprintf (stderr, "error in deriv5\n"); 
+	  } else if (isnan(deriv->data.F32[6])) { 
+	    fprintf (stderr, "error in deriv6\n"); 
+	  } else if (isnan(deriv->data.F32[7])) { 
+	    fprintf (stderr, "error in deriv7\n"); 
+	  } else if (isnan(deriv->data.F32[8])) { 
+	    fprintf (stderr, "error in deriv8\n"); 
+	  } 
+ 
+ 
+	} 
+    } 
+    return(f); 
+} 
+ 
+//fixed  
+// XXX this needs to apply the axis ratio limits to prevent avoid solutions
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta) {
+ 
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+    float f1, f2, q1;
+    float q2 = 0;
+ 
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+	f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+	f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+	q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+	assert (q1 > 0);
+	q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+	switch (nParam) {
+	  case PM_PAR_SKY:  beta_lim = 1000;   break;
+	  case PM_PAR_I0:   beta_lim = 10000;    break; // too small?
+	  case PM_PAR_XPOS: beta_lim = 50;     break;
+	  case PM_PAR_YPOS: beta_lim = 50;     break;
+	  case PM_PAR_SXX:  beta_lim = 0.5;    break;
+	  case PM_PAR_SYY:  beta_lim = 0.5;    break;
+	  case PM_PAR_SXY:  beta_lim = 1.0;    break;  // set this to q2?
+	  case 7:           beta_lim = 10.0;     break;
+	  case 8:           beta_lim = M_PI/6.0; break;
+
+	  default:
+	    psAbort("invalid parameter %d for beta test", nParam);
+	}
+	if (fabs(beta[nParam]) > fabs(beta_lim)) {
+	    beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+	    return false;
+	}
+	return true;
+      case PS_MINIMIZE_PARAM_MIN:
+	switch (nParam) {
+	  case PM_PAR_SKY:  params_min = -1000; break;
+	  case PM_PAR_I0:   params_min =     0; break;
+	  case PM_PAR_XPOS: params_min =  -100; break;
+	  case PM_PAR_YPOS: params_min =  -100; break;
+	  case PM_PAR_SXX:  params_min =   0.5; break;
+	  case PM_PAR_SYY:  params_min =   0.5; break;
+	  case PM_PAR_SXY:  params_min =  -5.0; break; // set this to -q2?
+	  case 7:           params_min =     0;  break;
+	  case 8:           params_min = -1*M_PI; break;
+
+	  default:
+	    psAbort("invalid parameter %d for param min test", nParam);
+	}
+	if (params[nParam] < params_min) {
+	    params[nParam] = params_min;
+	    return false;
+	}
+	return true;
+      case PS_MINIMIZE_PARAM_MAX:
+	switch (nParam) {
+	  case PM_PAR_SKY:  params_max =   1e5; break;
+	  case PM_PAR_I0:   params_max =   1e8; break;
+	  case PM_PAR_XPOS: params_max =   1e4; break;
+	  case PM_PAR_YPOS: params_max =   1e4; break;
+	  case PM_PAR_SXX:  params_max =   100; break;
+	  case PM_PAR_SYY:  params_max =   100; break;
+	  case PM_PAR_SXY:  params_max =   +q2; break;
+	  case 7:           params_max =   150; break;
+	  case 8:           params_max =  M_PI; break;
+	  default:
+	    psAbort("invalid parameter %d for param max test", nParam);
+	}
+	if (params[nParam] > params_max) {
+	    params[nParam] = params_max;
+	    return false;
+	}
+	return true;
+      default:
+	psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+//fixed 
+psF64 PM_MODEL_FLUX(const psVector *params) 
+{ 
+    float f, norm, z; 
+ 
+    psF32 *PAR = params->data.F32; 
+ 
+    psF64 A1   = PS_SQR(PAR[4]); 
+    psF64 A2   = PS_SQR(PAR[5]); 
+    psF64 A3   = PS_SQR(PAR[6]); 
+    psF32 Rx=2./PS_SQR(PAR[4]);  
+    psF32 Ry=2./PS_SQR(PAR[5]);  
+    psF32 Rxy=PAR[6]; 
+ 
+ 
+    psF32 theta = PAR[8];  
+    psF32 sinT=sin(theta); 
+    psF32 cosT=cos(theta); 
+ 
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y 
+ 
+    psF64 A4   = Syrot*PAR[7]; 
+ 
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3) + A4; 
+    // Area is equivalent to 2 pi sigma^2 + rectangle 
+ 
+    // the area needs to be multiplied by the integral of f(z) 
+    norm = 0.0; 
+    for (z = 0.005; z < 50; z += 0.01) { 
+	f = 1.0 / (1 + z + z*z/2 + z*z*z/6); 
+	norm += f; 
+    } 
+    norm *= 0.01; 
+     
+    psF64 Flux = params->data.F32[1] * Area * norm; 
+ 
+    return(Flux); 
+} 
+ 
+// define this function so it never returns Inf or NaN 
+// also prevent 0 returns, and just send a v small number 
+// return the radius which yields the requested flux 
+ 
+//fixed, but need to change how it is called to accomodate 2 radii 
+psF64 PM_MODEL_RADIUS  (const psVector *params, psF64 flux) 
+{ 
+    if (flux <= 0) return (1.0); 
+    if (params->data.F32[1] <= 0) return (1.0); 
+    if (flux >= params->data.F32[1]) return (1.0); 
+ 
+    psF32 *PAR = params->data.F32; 
+    psF32 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]); 
+ 
+    psF32 theta = PAR[8];  
+    psF32 sinT=sin(theta); 
+    psF32 cosT=cos(theta); 
+    psF32 Rx=2./PS_SQR(PAR[4]); 
+    psF32 Ry=2./PS_SQR(PAR[5]); 
+    psF32 Rxy=PAR[6]; 
+    psF32 length=PAR[7]; 
+ 
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y 
+ 
+    psF64 radius = 0; 
+    if (flux > 0){ 
+      psF64 radius0 = sigma * sqrt (2.0 * log(params->data.F32[1] / flux)); 
+      psF64 radius1 = Syrot * sqrt (2.0 * log(params->data.F32[1] / flux)); 
+ 
+      if (radius0 > radius1) { 
+	radius=radius0+length/2.0; 
+      } else { 
+	radius=radius1+length/2.0; 
+      }  
+    } else { 
+      radius = 1000; 
+    } 
+ 
+    if (radius < 0.01){ 
+      radius = 0.01; 
+    } 
+ 
+    if (isnan(radius)) { 
+      fprintf (stderr, "error in code\n"); 
+    } 
+    return (radius); 
+} 
+ 
+//fixed I think...no good way of guessing as far as I can tell 
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source) { 
+ 
+    pmMoments *Smoments = source->moments; 
+    psF32     *params  = model->params->data.F32; 
+ 
+    psEllipseAxes axes; 
+    psEllipseShape shape; 
+    psEllipseMoments moments; 
+ 
+    moments.x2 = PS_SQR(Smoments->Sx); 
+    moments.y2 = PS_SQR(Smoments->Sy); 
+    moments.xy = Smoments->Sxy; 
+    //sometimes these moment inputs are zero...why? 
+ 
+    // solve the math to go from Moments To Shape 
+    // limit the axis ratio to 20 (a guess)
+    axes = psEllipseMomentsToAxes(moments, 20.0); 
+    shape = psEllipseAxesToShape(axes); 
+ 
+    params[0] = Smoments->Sky; 
+    params[1] = Smoments->Peak - Smoments->Sky; 
+    params[2] = Smoments->x; 
+    params[3] = Smoments->y; 
+    params[7] = 2 * axes.major;  
+    params[8] = axes.theta; 
+ 
+    if (moments.x2 == 0 || moments.y2 == 0){ 
+      params[4] = 2.0; 
+      params[5] = 2.0; 
+      params[6] = 0.0; 
+    } else { 
+      params[4] = 1.0 / shape.sx; 
+      params[5] = 1.0 / shape.sy; 
+      params[6] = shape.sxy; 
+    } 
+ 
+    //    printf("Who is NaN? momx: %4.3f  momy: %4.3f  momxy: %4.3f\n", moments.x2,moments.y2, moments.xy); 
+ 
+    return(true); 
+} 
+ 
+//fixed 
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) { 
+ 
+    psF32 *out = modelPSF->params->data.F32; 
+    psF32 *in  = modelFLT->params->data.F32; 
+     
+    out[0] = in[0]; 
+    out[1] = in[1]; 
+    out[2] = in[2]; 
+    out[3] = in[3]; 
+    out[7] = in[7]; 
+    out[8] = in[8]; 
+ 
+    for (int i = 4; i < 7; i++) { 
+      psPolynomial2D *poly = psf->params_NEW->data[i-4]; 
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]); 
+    } 
+    return(true); 
+} 
+ 
+//done I think 
+bool PM_MODEL_FIT_STATUS (pmModel *model) { 
+ 
+    psF32 dP; 
+    bool  status; 
+ 
+    psF32 *PAR  = model->params->data.F32; 
+    psF32 *dPAR = model->dparams->data.F32; 
+ 
+    dP = 0; 
+    dP += PS_SQR(dPAR[4] / PAR[4]); 
+    dP += PS_SQR(dPAR[5] / PAR[5]); 
+    dP = sqrt (dP); 
+ 
+    status = true; 
+    status &= (dP < 0.5); 
+    status &= (PAR[1] > 0); 
+    status &= ((dPAR[1]/PAR[1]) < 0.5); 
+    //    status &= ((dPAR[7]/PAR[7]) < 0.5); 
+    //    status &= ((dPAR[8]/PAR[8]) < 0.5); 
+ 
+    if (status) return true; 
+    return false; 
+} 
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/models/pmModel_TEST1.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/models/pmModel_TEST1.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/models/pmModel_TEST1.c	(revision 21632)
@@ -0,0 +1,267 @@
+/******************************************************************************
+ * this file defines the TEST1 source shape model.  Note that these model functions are loaded
+ * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
+
+ * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+ * PM_PAR_I0 1    - central intensity
+ * PM_PAR_XPOS 2  - X center of object
+ * PM_PAR_YPOS 3  - Y center of object
+ * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) * SigmaX)
+ * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) * SigmaY)
+ * PM_PAR_SXY 6   - X*Y term of elliptical contour
+ *****************************************************************************/
+
+# define PM_MODEL_FUNC       pmModelFunc_TEST1
+# define PM_MODEL_FLUX       pmModelFlux_TEST1
+# define PM_MODEL_GUESS      pmModelGuess_TEST1
+# define PM_MODEL_LIMITS     pmModelLimits_TEST1
+# define PM_MODEL_RADIUS     pmModelRadius_TEST1
+# define PM_MODEL_FROM_PSF   pmModelFromPSF_TEST1
+# define PM_MODEL_FIT_STATUS pmModelFitStatus_TEST1
+
+// XXX consider changing to form PAR[SXY]*(1/PAR[SXX]^2 + 1/PAR[SYY]^2)*X*Y
+// this would provide natural limits on PAR[SXY] of -0.25 : +0.25
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                         const psVector *params,
+                         const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    // XXX this is fitting sigma_x/sqrt(2), sigma_y/sqrt(2)
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    psF32 t  = 1 + z + z*z/2.0;
+    psF32 r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+        psF32 q = PAR[PM_PAR_I0]*r*r*t;
+        dPAR[PM_PAR_SKY]  = +1.0;
+        dPAR[PM_PAR_I0]   = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_SXX]  = +2.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY]  = +2.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY]  = -q*X*Y;
+    }
+    return(f);
+}
+
+// define the parameter limits
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta) 
+{
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+	switch (nParam) {
+	  case PM_PAR_SKY:  beta_lim = 1000;  break;
+	  case PM_PAR_I0:   beta_lim = 3e6;   break;
+	  case PM_PAR_XPOS: beta_lim = 5;     break;
+	  case PM_PAR_YPOS: beta_lim = 5;     break;
+	  case PM_PAR_SXX:  beta_lim = 0.5;   break;
+	  case PM_PAR_SYY:  beta_lim = 0.5;   break;
+	  case PM_PAR_SXY:  beta_lim = 0.5;   break;
+	  default:
+	    psAbort("invalid parameter %d for beta test", nParam);
+	}
+	if (fabs(beta[nParam]) > fabs(beta_lim)) {
+	    beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+	    return false;
+	}
+	return true;
+      case PS_MINIMIZE_PARAM_MIN:
+	switch (nParam) {
+	  case PM_PAR_SKY:  params_min = -1000; break;
+	  case PM_PAR_I0:   params_min =     0; break;
+	  case PM_PAR_XPOS: params_min =  -100; break;
+	  case PM_PAR_YPOS: params_min =  -100; break;
+	  case PM_PAR_SXX:  params_min =   0.5; break;
+	  case PM_PAR_SYY:  params_min =   0.5; break;
+	  case PM_PAR_SXY:  params_min =  -5.0; break;
+	  default:
+	    psAbort("invalid parameter %d for param min test", nParam);
+	}
+	if (params[nParam] < params_min) {
+	    params[nParam] = params_min;
+	    return false;
+	}
+	return true;
+      case PS_MINIMIZE_PARAM_MAX:
+	switch (nParam) {
+	  case PM_PAR_SKY:  params_max =   1e5; break;
+	  case PM_PAR_I0:   params_max =   1e8; break;
+	  case PM_PAR_XPOS: params_max =   1e4; break;
+	  case PM_PAR_YPOS: params_max =   1e4; break;
+	  case PM_PAR_SXX:  params_max =   100; break;
+	  case PM_PAR_SYY:  params_max =   100; break;
+	  case PM_PAR_SXY:  params_max =  +5.0; break;
+	  default:
+	    psAbort("invalid parameter %d for param max test", nParam);
+	}
+	if (params[nParam] > params_max) {
+	    params[nParam] = params_max;
+	    return false;
+	}
+	return true;
+      default:
+	psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+
+    pmMoments *moments = source->moments;
+    psF32     *PAR  = model->params->data.F32;
+
+    PAR[PM_PAR_SKY] = moments->Sky;
+    PAR[PM_PAR_I0] = moments->Peak - moments->Sky;
+    PAR[PM_PAR_XPOS] = moments->x;
+    PAR[PM_PAR_YPOS] = moments->y;
+    PAR[PM_PAR_SXX] = PS_MAX(0.5, moments->Sx);
+    PAR[PM_PAR_SYY] = PS_MAX(0.5, moments->Sy);
+    PAR[PM_PAR_SXY] = 0.0;  // XXX we can get this right if we do the integral
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float norm, z;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+    # define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        z += DZ;
+        f2 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[PM_PAR_I0])
+        return (1.0);
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // this estimates the radius assuming f(z) is roughly exp(-z)
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 radius = axes.major * sqrt (2.0 * log(params->data.F32[PM_PAR_I0] / flux));
+
+    if (isnan(radius)) psAbort("error in code: never return invalid radius");
+    if (radius < 0) psAbort("error in code: never return invalid radius");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params_NEW->n > PM_PAR_YPOS);
+    assert (psf->params_NEW->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params_NEW->n; i++) {
+	if (psf->params_NEW->data[i] == NULL) {
+	    out[i] = in[i];
+	} else {	    
+	    psPolynomial2D *poly = psf->params_NEW->data[i];
+	    out[i] = psPolynomial2DEval(poly, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+	}
+    }
+
+    // the 2D model for SXY actually fits SXY / (SXX^-2 + SYY^-2); correct here
+    out[PM_PAR_SXY] = pmPSF_SXYtoModel (out);
+
+    return(true);
+}
+
+// XXX double-check these definitions below
+// this test is invalid if the parameters are derived
+// from the PSF model
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphot.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphot.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphot.c	(revision 21632)
@@ -0,0 +1,38 @@
+# include "psphot.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: psphot [-file image(s)] [-list imagelist] (output)\n");
+    exit (PS_EXIT_CONFIG_ERROR);
+}
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    psphotErrorRegister();              // register our error codes/messages
+    psphotModelGroupInit ();		// load implementation-specific models
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotArguments (argc, argv);
+    if (!config) usage ();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphot.h
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphot.h	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphot.h	(revision 21632)
@@ -0,0 +1,103 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include <psmodules.h>
+
+# include "psphotErrorCodes.h"
+#define PSPHOT_RECIPE "PSPHOT" // Name of the recipe to use
+
+// top-level psphot functions
+const char *psphotCVSName(void);
+psString psphotVersion(void);
+psString psphotVersionLong(void);
+
+// XXX test functions
+bool psphotTestPSF (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool pmPSFtestModel (psArray *sources, char *modelName, float RADIUS, bool poissonErrors, psPolynomial2D *psfTrendMask);
+
+pmConfig       *psphotArguments (int argc, char **argv);
+bool            psphotParseCamera (pmConfig *config);
+bool            psphotImageLoop (pmConfig *config);
+
+bool            psphotModelTest (pmReadout *readout, psMetadata *recipe);
+bool            psphotReadout (pmConfig *config, pmFPAview *view);
+void            psphotCleanup (pmConfig *config);
+
+psArray        *psphotFakeSources ();
+
+// psphotReadout functions
+bool            psphotImageMedian (pmConfig *config, pmFPAview *view);
+psArray        *psphotFindPeaks (pmReadout *readout, psMetadata *recipe, int pass);
+psArray        *psphotSourceStats (pmReadout *readout, psMetadata *recipe, psArray *allpeaks);
+bool            psphotRoughClass (psArray *sources, psMetadata *recipe);
+bool            psphotBasicDeblend (psArray *sources, psMetadata *recipe);
+pmPSF          *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf);
+bool            psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources);
+bool            psphotEnsemblePSF (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final);
+bool            psphotGuessModels (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotReplaceUnfit (psArray *sources);
+bool            psphotReplaceAll (psArray *sources);
+bool            psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotMagnitudes (psArray *sources, psMetadata *recipe, pmPSF *psf, pmReadout *background);
+bool            psphotSkyReplace (pmConfig *config, pmFPAview *view);
+bool            psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmPSF *psf, psArray *sources);
+
+// basic support functions
+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, bool ignore);
+void            psphotTestArguments (int *argc, char **argv);
+bool            psphotMaskReadout (pmReadout *readout, psMetadata *recipe);
+void            psphotSourceFreePixels (psArray *sources);
+
+// functions to set the correct source pixels
+bool            psphotInitRadiusPSF (const psMetadata *recipe, const pmModelType type);
+bool            psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model);
+bool            psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR);
+bool            psphotInitRadiusEXT (psMetadata *recipe, pmModelType type);
+bool            psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model);
+
+// output functions
+bool            psphotAddPhotcode (psMetadata *recipe, pmConfig *config, pmFPAview *view);
+bool            psphotDumpMoments (psMetadata *recipe, psArray *sources);
+psMetadata     *psphotDefineHeader (psMetadata *recipe);
+bool            psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+int             psphotSaveImage (psMetadata *header, psImage *image, char *filename);
+bool psphotDumpConfig (pmConfig *config);
+pmReadout *psphotSelectBackground (pmConfig *config, pmFPAview *view);
+
+// PSF / DBL / EXT evaluation functions
+bool            psphotEvalPSF (pmSource *source, pmModel *model);
+bool            psphotEvalDBL (pmSource *source, pmModel *model);
+bool            psphotEvalEXT (pmSource *source, pmModel *model);
+
+//  functions to support the source fitting process
+bool            psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout);
+bool            psphotInitLimitsEXT (psMetadata *recipe);
+bool            psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf);
+bool            psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf);
+bool            psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf);
+pmModel        *psphotFitEXT (pmReadout *readout, pmSource *source);
+psArray        *psphotFitDBL (pmReadout *readout, pmSource *source);
+
+// functions to support simultaneous multi-source fitting
+bool            psphotFitSet (pmSource *oneSrc, pmModel *oneModel, char *fitset, pmSourceFitMode mode);
+
+// plotting functions (available if libkapa is installed)
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources);
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources);
+
+bool psphotFitInit ();
+bool psphotFitSummary ();
+bool psphotMergeSources (psArray *oldSources, psArray *newSources);
+bool psphotLoadExtSources (pmConfig *config, pmFPAview *view, psArray *sources);
+psExit psphotGetExitStatus ();
+bool psphotSetHeaderNstars (psMetadata *recipe, psArray *sources);
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotApResid.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotApResid.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotApResid.c	(revision 21632)
@@ -0,0 +1,356 @@
+# include "psphot.h"
+// XXXX this code fails if there are too few sources to measure the aperture residual
+// the larger problem is that the rules for accepting more polynomial terms are weak.
+
+static pmPSFApTrendOptions DEFAULT_OPTION = PM_PSF_APTREND_SKYBIAS;
+
+// measure the aperture residual statistics
+bool psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf)
+{
+    int Nfail = 0;
+    int Nskip = 0;
+    int Npsf;
+    bool status;
+    pmModel *model;
+    pmSource *source;
+
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+    PS_ASSERT_PTR_NON_NULL(psf->ApTrend, false);
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+
+    psTimerStart ("psphot");
+
+    // S/N limit to perform full non-linear fits
+    float MAX_AP_OFFSET = psMetadataLookupF32 (&status, recipe, "MAX_AP_OFFSET");
+
+    // this is the smallest radius allowed: need to at least extend growth curve down to this...
+    float PSF_FIT_PAD   = psMetadataLookupF32 (&status, recipe, "PSF_FIT_PADDING");
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+    int NSTAR_APERTURE_CORRECTION_MIN =
+	psMetadataLookupS32(&status, recipe, "NSTAR_APERTURE_CORRECTION_MIN");
+    if (!status) {
+	NSTAR_APERTURE_CORRECTION_MIN = 5;
+    }
+
+    pmSourcePhotometryMode photMode = 0;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+
+    // set limits on the aperture magnitudes
+    pmSourceMagnitudesInit (recipe);
+
+    // measure the aperture loss as a function of radius for PSF
+    float REF_RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_REF_RADIUS");
+    psf->growth = pmGrowthCurveAlloc (PSF_FIT_PAD, 100.0, REF_RADIUS);
+
+    pmGrowthCurveGenerate (readout, psf, IGNORE_GROWTH);
+
+    psVector *mask    = psVectorAllocEmpty (300, PS_TYPE_U8);
+    psVector *xPos    = psVectorAllocEmpty (300, PS_TYPE_F64);
+    psVector *yPos    = psVectorAllocEmpty (300, PS_TYPE_F64);
+    psVector *flux    = psVectorAllocEmpty (300, PS_TYPE_F64);
+    psVector *r2rflux = psVectorAllocEmpty (300, PS_TYPE_F64);
+    psVector *apResid = psVectorAllocEmpty (300, PS_TYPE_F64);
+    psVector *dMag    = psVectorAllocEmpty (300, PS_TYPE_F64);
+    Npsf = 0;
+
+    // select all good PM_SOURCE_TYPE_STAR entries
+    for (int i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        model = source->modelPSF;
+
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+        if (source->mode &  PM_SOURCE_MODE_SATSTAR) continue;
+        if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        if (source->mode &  PM_SOURCE_MODE_FAIL) continue;
+        if (source->mode &  PM_SOURCE_MODE_POOR) continue;
+
+        // get growth-corrected, apTrend-uncorrected magnitudes in scaled apertures
+        // will fail if below S/N threshold or model is missing
+        if (!pmSourceMagnitudes (source, psf, photMode)) {
+            Nskip ++;
+            continue;
+        }
+
+        apResid->data.F64[Npsf] = source->apMag - source->psfMag;
+
+        xPos->data.F64[Npsf]    = model->params->data.F32[PM_PAR_XPOS];
+        yPos->data.F64[Npsf]    = model->params->data.F32[PM_PAR_YPOS];
+
+        flux->data.F64[Npsf]    = pow(10.0, -0.4*source->psfMag);
+        r2rflux->data.F64[Npsf] = PS_SQR(model->radiusFit) / flux->data.F64[Npsf];
+
+        mask->data.U8[Npsf] = 0;
+
+        // XXX sanity clip?
+        // XXX need to see if all data were tossed?
+        // XXX need to subtract median?
+        // XXX need to put this in the config data...
+        // if (fabs(apResid->data.F64[Npsf]) > 0.2) continue;
+        if ((MAX_AP_OFFSET > 0) && (fabs(apResid->data.F64[Npsf]) > MAX_AP_OFFSET)) {
+            Nfail ++;
+            continue;
+        }
+
+        dMag->data.F64[Npsf] = model->dparams->data.F32[PM_PAR_I0] / model->params->data.F32[PM_PAR_I0];
+
+        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", PS_LOG_DETAIL, "measure aperture residuals for %d objects (%d skipped, %d failed, %ld invalid)\n",
+              Npsf, Nskip, Nfail, sources->n - Npsf - Nskip - Nfail);
+
+    // XXX choose a better value here?
+    if (Npsf < NSTAR_APERTURE_CORRECTION_MIN) {
+        psError(PSPHOT_ERR_APERTURE, true, "Only %d valid aperture residual sources (need %d), giving up",
+		Npsf, NSTAR_APERTURE_CORRECTION_MIN);
+        return false;
+    }
+
+    // APTREND options : NONE CONSTANT SKYBIAS XY_LIN XY_QUAD SKY_XY_LIN FULL
+    // APTREND options are used in the switch block below
+    pmPSFApTrendOptions ApTrendOption = DEFAULT_OPTION;
+    char *optionName = psMetadataLookupPtr (&status, recipe, "APTREND");
+    if (status) ApTrendOption = pmPSFApTrendOptionFromName (optionName);
+    if (ApTrendOption == PM_PSF_APTREND_ERROR) {
+        psError(PSPHOT_ERR_APERTURE, true, "invalid aperture residual trend %s", optionName);
+        return false;
+    }
+
+    // 3hi/1lo sigma clipping on the rflux vs metric fit
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    stats->min = 2.0;
+    stats->max = 3.0;
+
+    // no correction
+    switch (ApTrendOption) {
+      case PM_PSF_APTREND_NONE:
+        // remove ApTrend fit from pmPSFtry
+        psf->ApTrend->coeff[0][0][0][0] = 0;
+        break;
+      case PM_PSF_APTREND_CONSTANT:
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting aperture correction");
+	    return false;
+	}
+	break;
+      case PM_PSF_APTREND_SKYBIAS:
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_SKYBIAS);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting sky bias");
+	    return false;
+	}
+	break;
+      case PM_PSF_APTREND_SKYSAT:
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_SKYBIAS);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting sky bias");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_SKYSAT);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting skysat");
+	    return false;
+	}
+	break;
+      case PM_PSF_APTREND_XY_LIN:
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_XY_LIN);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "fitting, XY_LIN");
+	    return false;
+	}
+	break;
+      case PM_PSF_APTREND_XY_QUAD:
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_XY_QUAD);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting XY_QUAD");
+	    return false;
+	}
+	break;
+      case PM_PSF_APTREND_SKY_XY_LIN:
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_SKY_XY_LIN);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting sky xy_lin");
+	    return false;
+	}
+	break;
+      case PM_PSF_APTREND_SKYSAT_XY_LIN:
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_SKYBIAS);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting sky bias");
+	    return false;
+	}
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_SKYSAT_XY_LIN);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting skyset xy_lin");
+	    return false;
+	}
+	break;
+      case PM_PSF_APTREND_ALL:
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_CONSTANT);
+	if (!psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Failed to measure apTrend");
+	    return false;
+	}
+	// fit just SkyBias and clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_SKYBIAS);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "fitting skyBias");
+	    return false;
+	}
+	// finally, fit x, y, SkyBias and clip out objects which are too far from the median 
+	stats->clipIter = 3;
+	pmPSFMaskApTrend (psf->ApTrend, PM_PSF_APTREND_ALL);
+	if (!psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux)) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "fitting all");
+	    return false;
+	}
+	break;
+      default:
+        psError(PSPHOT_ERR_PHOTOM, true, "Unknown APTREND value: %s", optionName);
+        return false;
+    }
+
+    // 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);
+
+    // measure scatter for sources with dMag < 0.01 (S/N = 100)
+    int Nkeep = 0;
+    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;
+        }
+        if (! mask->data.U8[i]) Nkeep ++;
+    }
+    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 = Npsf;
+
+    // save results for later output
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skyBias);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYSAT",   PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skySat);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->growth->apLoss);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "ap residual scatter", psf->nApResid);
+
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "measure full-frame aperture residuals for %d of %d objects: %f sec\n", 
+	      Nkeep, Npsf, psTimerMark ("psphot"));
+    psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "aperture residual: %f +/- %f : %f bias, %f skysat\n",
+              psf->ApResid, psf->dApResid, psf->skyBias, psf->skySat);
+    psLogMsg ("psphot.apresid", PS_LOG_MINUTIA, "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: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotArguments.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotArguments.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotArguments.c	(revision 21632)
@@ -0,0 +1,124 @@
+# include "psphot.h"
+
+pmConfig *psphotArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-version"))) {
+        psString version;
+        version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+        psError(PSPHOT_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // run the test model (requires X,Y coordinate)
+    if ((N = psArgumentGet (argc, argv, "-modeltest"))) {
+        psMetadataAddBool (options, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_Y", 0, "", atof(argv[N+2]));
+
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+
+        // specify the modeltest model
+        if ((N = psArgumentGet (argc, argv, "-model"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODEL", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+
+        // specify the test fit mode
+        if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+        if ((N = psArgumentGet (argc, argv, "-fitset"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+    }
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // fitmode : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "FITMODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // drop the local view on the options (saved in config->arguments:PSPHOT_RECIPE)
+    psFree (options);
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    pmConfigFileSetsMD (config->arguments, config, "MASK",   "-mask", "-masklist");
+    pmConfigFileSetsMD (config->arguments, config, "WEIGHT", "-weight", "-weightlist");
+    pmConfigFileSetsMD (config->arguments, config, "PSF",    "-psf", "-psflist");
+    pmConfigFileSetsMD (config->arguments, config, "SRC",    "-src", "-srclist");
+
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, config, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+        return NULL;
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+        return NULL;
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotArguments...\n");
+    return (config);
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotBasicDeblend.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotBasicDeblend.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotBasicDeblend.c	(revision 21632)
@@ -0,0 +1,130 @@
+# include "psphot.h"
+
+// 2006.02.07 : no leaks
+bool psphotBasicDeblend (psArray *sources, psMetadata *recipe) {
+
+    int N;
+    bool status;
+    float threshold;
+    pmSource *source, *testSource;
+
+    int Nblend = 0;
+
+    psTimerStart ("psphot");
+
+    float FRACTION = psMetadataLookupF32 (&status, recipe, "DEBLEND_PEAK_FRACTION");
+    if (!status) FRACTION = 0.25;
+
+    float NSIGMA   = psMetadataLookupF32 (&status, recipe, "DEBLEND_SKY_NSIGMA");
+    if (!status) NSIGMA = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, 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->SN;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+        N = index->data.U32[i];
+        source = sources->data[N];
+
+        if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+
+        // temporary array for overlapping objects we find
+        psArray *overlap = psArrayAllocEmpty (100);
+
+        // search backwards for overlapping sources
+        for (int j = N - 1; j >= 0; j--) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) break;
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        // search forwards for overlapping sources
+        for (int j = N + 1; j < sources->n; j++) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        if (overlap->n == 0) {
+            psFree (overlap);
+            continue;
+        }
+
+        // this source has overlapping neighbors, check for actual blends
+        // generate source contour (1/4 peak counts)
+        // set the threshold based on user inputs
+
+        // threshold is fraction of the source peak flux
+        // image is background subtracted; source->moments->Sky should always be 0.0
+        threshold = FRACTION * source->peak->SN;
+        // threshold is no less than NSIGMA
+        threshold = PS_MAX (threshold, NSIGMA);
+
+        // generate a basic contour (set of x,y coordinates at-or-below flux level)
+        psArray *contour = pmSourceContour (source->pixels, source->peak->x, source->peak->y, threshold);
+        if (contour == NULL) {
+            psFree (overlap);
+            continue;
+        }
+
+        // the source contour consists of two vectors, xv and yv.  the contour is
+        // a series of coordinate pairs, (xv[i],yv[i]) & (xv[i+1],yv[i+1]).  both
+        // coordinate pairs have the same yv[] value, with xv[i] corresponding to
+        // the left boundary and xv[i+1] corresponding to the right boundary
+
+	// a blend can only be associated with one primary source
+
+        psVector *xv = contour->data[0];
+        psVector *yv = contour->data[1];
+        for (int k = 0; k < overlap->n; k++) {
+            testSource = overlap->data[k];
+	    if (testSource->mode & PM_SOURCE_MODE_BLEND) continue;
+            if (testSource->peak->value > source->peak->value) continue;
+            for (int j = 0; j < xv->n; j+=2) {
+                if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+                if (xv->data.F32[j+0] > testSource->peak->x) break;
+                if (xv->data.F32[j+1] < testSource->peak->x) break;
+
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+
+                // add this to the list of source->blends
+                if (source->blends == NULL) {
+                    source->blends = psArrayAllocEmpty (16);
+                }
+                psArrayAdd (source->blends, 16, testSource);
+
+                Nblend ++;
+                j = xv->n;
+            }
+        }
+        psFree (overlap);
+        psFree (contour);
+    }
+    psLogMsg ("psphot.deblend", PS_LOG_INFO, "identified %d blended objects: %f sec\n", Nblend, psTimerMark ("psphot"));
+
+    psFree (SN);
+    psFree (index);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotBlendFit.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotBlendFit.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotBlendFit.c	(revision 21632)
@@ -0,0 +1,94 @@
+# include "psphot.h"
+
+// XXX I don't like this name
+bool psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) { 
+
+    int Nfit = 0;
+    int Npsf = 0;
+    int Next = 0;
+    int Nfail = 0;
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // 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, recipe, "FULL_FIT_SN_LIM");
+
+    psphotInitLimitsPSF (recipe, readout);
+    psphotInitLimitsEXT (recipe);
+    psphotInitRadiusPSF (recipe, psf->type);
+
+    psphotFitInit ();
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    for (int i = 0; i < sources->n; i++) {
+	// if (i%100 == 0) psphotFitSummary ();
+
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+	if (source->type == PM_SOURCE_TYPE_DEFECT) continue; 
+	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+	// skip DBL second sources (ie, added by psphotFitBlob)
+	if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+
+	// limit selection to some SN limit
+	if (source->moments == NULL) continue;
+	if (source->moments->SN < FIT_SN_LIM) 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;
+
+	// if model is NULL, we don't have a starting guess
+	if (source->modelPSF == NULL) continue;
+
+	// skip sources which are insignificant flux?
+	if (source->modelPSF->params->data.F32[1] < 0.1) {
+	    psTrace ("psphot", 5, "skipping near-zero source: %f, %f : %f\n",
+		     source->modelPSF->params->data.F32[1],
+		     source->modelPSF->params->data.F32[2],
+		     source->modelPSF->params->data.F32[3]);
+	    continue;
+	}
+
+	// replace object in image
+	if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+	    pmModelAdd (source->pixels, source->mask, source->modelPSF, false, false);
+	}
+	Nfit ++;
+
+	// try fitting PSFs, then try extended sources
+	if (psphotFitBlend (readout, source, psf)) { 
+	    psTrace ("psphot", 5, "source at %7.1f, %7.1f is psf", source->moments->x, source->moments->y);
+	    Npsf ++;
+	    continue;
+	}
+	if (psphotFitBlob (readout, source, sources, psf)) {
+	    psTrace ("psphot", 5, "source at %7.1f, %7.1f is ext", source->moments->x, source->moments->y);
+	    Next ++;
+	    continue;
+	}
+
+	psTrace ("psphot", 5, "source at %7.1f, %7.1f failed", source->moments->x, source->moments->y);
+	Nfail ++;
+
+	// re-subtract PSF for object, leave local sky
+	pmModelSub (source->pixels, source->mask, source->modelPSF, false, false);
+	source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+	source->mode |= PM_SOURCE_MODE_TEMPSUB;
+    }
+
+    psLogMsg ("psphot.psphotBlendFit", PS_LOG_INFO, "fit models: %f sec for %d objects (%d psf, %d ext, %d failed, %ld skipped)\n", psTimerMark ("psphot"), Nfit, Npsf, Next, Nfail, sources->n - Nfit);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotChoosePSF.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotChoosePSF.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotChoosePSF.c	(revision 21632)
@@ -0,0 +1,358 @@
+# include "psphot.h"
+
+// try PSF models and select best option
+pmPSF *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool            status;
+    char           *modelName;
+    pmPSF          *psf = NULL;
+    pmPSFtry       *try = NULL;
+    psArray        *stars = NULL;
+
+    psTimerStart ("psphot");
+
+    // check if a PSF model is supplied by the user
+    psf = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.PSF");
+    if (psf != NULL) {
+        if (!psphotPSFstats (readout, recipe, psf)) psAbort("cannot measure PSF shape terms");
+	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, recipe, "PSF_MAX_NSTARS");
+    if (!status) {
+        NSTARS = PS_MIN (sources->n, 200);
+        psWarning("PSF_MAX_NSTARS is not set in the recipe --- defaulting to %d\n", NSTARS);
+    }
+
+    float PSF_MIN_DS = psMetadataLookupF32 (&status, recipe, "PSF_MIN_DS");
+    if (!status) {
+	PSF_MIN_DS = 0.01;
+        psWarning("PSF_MIN_DS is not set --- defaulting to %f\n", PSF_MIN_DS);
+    }
+
+    // supply the measured sky variance for optional constant errors (non-poissonian)
+    float SKY_SIG = psMetadataLookupF32 (&status, recipe, "SKY_SIG");
+    if (!status) {
+	SKY_SIG = 1.0;
+        psWarning("SKY_SIG is not set --- defaulting to %f\n", SKY_SIG);
+    }
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) {
+        POISSON_ERRORS = true;
+        psWarning("POISSON_ERRORS is not set in the recipe --- defaulting to true.\n");
+    }
+    pmSourceFitModelInit (15, 0.01, PS_SQR(SKY_SIG), POISSON_ERRORS);
+
+    // use poissonian errors or local-sky errors
+    bool PSF_PARAM_WEIGHTS = psMetadataLookupBool (&status, recipe, "PSF_PARAM_WEIGHTS");
+    if (!status) {
+        PSF_PARAM_WEIGHTS = false;
+        psWarning("PSF_PARAM_WEIGHTS is not set in the recipe --- defaulting to false.\n");
+    }
+
+    // how to model the PSF variations across the field
+    // XXX make a default value?  or not?
+    psMetadata *md = psMetadataLookupMetadata (&status, recipe, "PSF.TREND.MASK");
+    psPolynomial2D *psfTrendMask;
+    if (!status || !md) {
+        psWarning("PSF.TREND.MASK is not set in the recipe --- defaulting to use zeroth order.\n");
+        psfTrendMask = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 0, 0);
+    } else {
+        psfTrendMask = psPolynomial2DfromMetadata (md);
+        if (!psfTrendMask) {
+            psError(PSPHOT_ERR_PSF, true, "Unable to construct polynomial from PSF.TREND.MASK in the recipe");
+            return NULL;
+        }
+    }
+
+    stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; (i < sources->n) && (stars->n < NSTARS); i++) {
+        pmSource *source = sources->data[i];
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) psArrayAdd (stars, 200, source);
+    }
+    psLogMsg ("psphot.pspsf", PS_LOG_DETAIL, "selected candidate %ld PSF objects\n", stars->n);
+
+    if (stars->n == 0) {
+        psError(PSPHOT_ERR_PSF, true, "Failed to find any PSF candidates");
+        return NULL;
+    }
+
+    // get the fixed PSF fit radius
+    // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    if (!status) {
+        RADIUS = 20.0;
+        psWarning("PSF_FIT_RADIUS is not set in the recipe --- defaulting to %g\n", RADIUS);
+    }
+
+    // get the list pointers for the PSF_MODEL entries
+    psList *list = NULL;
+    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+    if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+
+    if (mdi->type == PS_DATA_STRING) {
+        list = psListAlloc(NULL);
+        psListAdd (list, PS_LIST_HEAD, mdi);
+    } else {
+        if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+        list = psMemIncrRefCounter(mdi->data.list);
+    }
+
+    // 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, POISSON_ERRORS, psfTrendMask, PSF_PARAM_WEIGHTS);
+    }
+
+    psFree (iter);
+    psFree (list);
+    psFree (stars);
+    psFree (psfTrendMask);
+
+    // select the best of the models
+    // here we are using the clippedStdev on the metric as the indicator
+    int bestN = -1;
+    float bestM = 0.0;
+    for (int i = 0; i < models->n; i++) {
+        try = models->data[i];
+        if (try == NULL) {
+            psError(PSPHOT_ERR_PSF, false, "PSF model %d is NULL", i);
+            continue;
+        }
+        float M = try->psf->dApResid;
+        if (bestN < 0 || M < bestM) {
+            bestM = M;
+            bestN = i;
+        }
+    }
+
+    // use the best model:
+    if (bestN < 0) {
+        psError (PSPHOT_ERR_DATA, false, "Failed to fit any models when choosing PSF");
+        psFree (models);
+        return NULL;
+    }
+
+    try = models->data[bestN];
+
+    // measure and save the median value of dparams[PM_PAR_SXX],dparams[PM_PAR_SYY]
+    // these are used by psphotEvalPSF to identify extended sources
+    psVector *Sx = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *Sy = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSx = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSy = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSN = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    for (int i = 0; i < try->sources->n; i++) {
+	// masked for: bad model fit, outlier in parameters
+	if (try->mask->data.U8[i] & PSFTRY_MASK_ALL)
+	    continue;
+
+	pmSource *source = try->sources->data[i];
+	Sx->data.F32[Sx->n] = source->modelPSF->params->data.F32[PM_PAR_SXX];
+	Sy->data.F32[Sy->n] = source->modelPSF->params->data.F32[PM_PAR_SYY];
+	dSN->data.F32[dSN->n] = source->modelPSF->dparams->data.F32[PM_PAR_I0] / source->modelPSF->params->data.F32[PM_PAR_I0];
+	dSx->data.F32[dSx->n] = source->modelPSF->dparams->data.F32[PM_PAR_SXX];
+	dSy->data.F32[dSy->n] = source->modelPSF->dparams->data.F32[PM_PAR_SYY];
+	Sx->n ++;
+	Sy->n ++;
+	dSN->n ++;
+	dSx->n ++;
+	dSy->n ++;
+    }
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    psVectorStats (stats, dSx, NULL, NULL, 0);
+    float dSxo = stats->sampleMean;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSx: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSx->n);
+
+    psVectorStats (stats, dSy, NULL, NULL, 0);
+    float dSyo = stats->sampleMean;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSy: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSy->n);
+
+    for (int i = 0; i < dSN->n; i++) {
+	dSx->data.F32[i] = (dSx->data.F32[i] - dSxo) / PS_MAX (PSF_MIN_DS, Sx->data.F32[i]*dSN->data.F32[i]);
+	dSy->data.F32[i] = (dSy->data.F32[i] - dSyo) / PS_MAX (PSF_MIN_DS, Sy->data.F32[i]*dSN->data.F32[i]);
+    }
+
+    psVectorStats (stats, dSx, NULL, NULL, 0);
+    float nSx = stats->sampleStdev;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSx: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSx->n);
+    psVectorStats (stats, dSy, NULL, NULL, 0);
+    float nSy = stats->sampleStdev;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSy: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSy->n);
+
+    psFree (Sx);
+    psFree (Sy);
+    psFree (dSx);
+    psFree (dSy);
+    psFree (dSN);
+    psFree (stats);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSX_MEAN", PS_META_REPLACE, "mean offset of dSXX", dSxo);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSY_MEAN", PS_META_REPLACE, "mean offset of dSYY", dSyo);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSX_STDV", PS_META_REPLACE, "stdev of mean-corrected dSXX", nSx);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSY_STDV", PS_META_REPLACE, "stdev of mean-corrected dSYY", nSy);
+
+    // unset the PSFSTAR flag for stars not used for PSF model
+    for (int i = 0; i < try->sources->n; i++) {
+        pmSource *source = try->sources->data[i];
+        if (try->mask->data.U8[i]) {
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+    }
+
+    // XXX test dump of psf star data and psf-subtracted image
+    if (psTraceGetLevel("psphot.psfstars") > 5) { 
+	for (int i = 0; i < try->sources->n; i++) {
+	    // masked for: bad model fit, outlier in parameters
+	    if (try->mask->data.U8[i] & PSFTRY_MASK_ALL)
+		continue;
+
+	    pmSource *source = try->sources->data[i];
+	    float x = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+	    float y = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+
+	    // set the mask and subtract the PSF model
+	    psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PM_MASK_MARK);
+	    pmModelSub (source->pixels, source->mask, source->modelPSF, false, false);
+	    psImageKeepCircle (source->mask, x, y, RADIUS, "AND", PS_NOT_U8(PM_MASK_MARK));
+	}
+
+	psphotSaveImage (NULL, readout->image,  "psfstars.fits");
+	pmSourcesWritePSFs (try->sources, "psfstars.dat");
+	pmSourcesWriteEXTs (try->sources, "extstars.dat", false);
+	psMetadata *psfData = pmPSFtoMetadata (NULL, try->psf);
+	psMetadataConfigWrite (psfData, "psfmodel.dat");
+	psFree (psfData);
+	psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "wrote out psf-subtracted image, psf data, exiting\n");
+	exit (0);
+    }
+
+    // save only the best model;
+    // XXX we are not saving the fitted sources
+    // XXX do we want to keep them so we may optionally write them out?
+    psf = psMemIncrRefCounter(try->psf);
+    psFree (models);
+
+    if (!psphotPSFstats (readout, recipe, psf)) psAbort("cannot measure PSF shape terms");
+
+    modelName = pmModelGetType (psf->type);
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "select psf model: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
+
+    return (psf);
+}
+
+// measure average parameters of the PSF model
+bool psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf) {
+
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+
+    psImage *image = readout->image;
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    pmModel *modelEXT = pmModelAlloc (psf->type);
+    PS_ASSERT_PTR_NON_NULL(modelEXT, false);
+
+    // make a model with unit central intensity at the image center
+    modelEXT->params->data.F32[PM_PAR_SKY] = 0;
+    modelEXT->params->data.F32[PM_PAR_I0] = 1;
+    modelEXT->params->data.F32[PM_PAR_XPOS] = 0.5*image->numCols;
+    modelEXT->params->data.F32[PM_PAR_YPOS] = 0.5*image->numRows;
+
+    // construct a PSF model at this coordinate
+    pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+    PS_ASSERT_PTR_NON_NULL(modelPSF, false);
+
+    // get the correct model-radius function
+    pmModelRadius modelRadiusFunc = pmModelRadius_GetFunction (psf->type);
+
+    // get the model full-width at half-max
+    psF64 FWHM_X = 2*modelRadiusFunc (modelPSF->params, 0.5);
+
+    // XXX make sure this is consistent with the re-definition of PM_PAR_SXX
+    shape.sx  = modelPSF->params->data.F32[PM_PAR_SXX];
+    shape.sy  = modelPSF->params->data.F32[PM_PAR_SYY];
+    shape.sxy = modelPSF->params->data.F32[PM_PAR_SXY];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    psF64 FWHM_Y = FWHM_X * (axes.minor / axes.major);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_X", 	PS_META_REPLACE, "PSF FWHM Major axis", FWHM_X);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_Y", 	PS_META_REPLACE, "PSF FWHM Minor axis", FWHM_Y);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",  	PS_META_REPLACE, "PSF angle",           axes.theta);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", psf->nPSFstars);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", true);
+
+    psFree (modelEXT);
+    psFree (modelPSF);
+
+    return true;
+}
+
+// determine approximate PSF shape parameters based on the moments
+bool psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources) {
+
+    // without the PSF model, we can only rely on the source->moments
+    // as a measure of the FWHM values
+
+    int FWHM_N = 0;
+    double FWHM_X = 0.0;
+    double FWHM_Y = 0.0;
+    double FWHM_T = 0.0;
+
+    psEllipseMoments moments;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!source) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // moments->Sx,Sy are roughly sigma_x,y
+        moments.x2 = PS_SQR(source->moments->Sx);
+        moments.y2 = PS_SQR(source->moments->Sy);
+        moments.xy = source->moments->Sxy;
+
+	// limit axis ratio < 20.0
+        axes = psEllipseMomentsToAxes (moments, 20.0);
+
+        FWHM_X += axes.major * 2.35;
+        FWHM_Y += axes.minor * 2.35;
+        FWHM_T += axes.theta;
+        FWHM_N ++;
+    }
+
+    FWHM_X /= FWHM_N;
+    FWHM_Y /= FWHM_N;
+    FWHM_T /= FWHM_N;
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_X",   PS_META_REPLACE, "PSF FWHM Major axis (from moments)", FWHM_X);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_Y",   PS_META_REPLACE, "PSF FWHM Minor axis (from moments)", FWHM_Y);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           FWHM_T);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", 0);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", false);
+
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotCleanup.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotCleanup.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotCleanup.c	(revision 21632)
@@ -0,0 +1,36 @@
+# include "psphot.h"
+
+void psphotCleanup (pmConfig *config) {
+
+    psFree (config);
+
+    psTimerStop ();
+    psMemCheckCorruption (stderr, true);
+    pmModelGroupCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "psphot");
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psphot");
+    return;
+}
+
+psExit psphotGetExitStatus () {
+
+    psErrorCode err = psErrorCodeLast ();
+    switch (err) {
+      case PS_ERR_NONE:
+	return PS_EXIT_SUCCESS;
+      case PSPHOT_ERR_SYS:
+        return PS_EXIT_SYS_ERROR;
+      case PSPHOT_ERR_CONFIG:
+        return PS_EXIT_CONFIG_ERROR;
+      case PSPHOT_ERR_PROG:
+        return PS_EXIT_PROG_ERROR;
+      case PSPHOT_ERR_DATA:
+        return PS_EXIT_DATA_ERROR;
+      default:
+        return PS_EXIT_UNKNOWN_ERROR;
+    }
+    return PS_EXIT_UNKNOWN_ERROR;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEnsemblePSF.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEnsemblePSF.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEnsemblePSF.c	(revision 21632)
@@ -0,0 +1,328 @@
+# include "psphot.h"
+
+// fit flux (and optionally sky model) to all reasonable sources
+// with the linear fitting process.  sources must have an associated
+// model with selected pixels, and the fit radius must be defined
+
+// given the set of sources, each of which points to the pixels in the 
+// science image, we construct a set of simulated sources with their own pixels.  
+// these are used to determine the simultaneous linear fit of fluxes.  
+// the analysis is performed wrt the simulated pixel values
+
+static bool SetBorderT (psSparseBorder *border, pmReadout *readout, psArray *refSources, psArray *fitSources, psVector *index, bool constant_weights, int SKY_FIT_ORDER);
+
+bool psphotEnsemblePSF (pmReadout *readout, psArray *refSources, psMetadata *recipe, pmPSF *psf, bool final) {
+
+    bool isPSF;
+    bool status;
+    float x;
+    float y;
+    float f;
+    // float r;
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in spatial order
+    refSources = psArraySort (refSources, psphotSortByY);
+
+    // storage arrays for fitSources and sequence index
+    psArray *fitSources = psArrayAllocEmpty (refSources->n);
+    psVector *index = psVectorAllocEmpty (refSources->n, PS_TYPE_U32);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionFromString (region);
+    AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    bool CONSTANT_PHOTOMETRIC_WEIGHTS =
+        psMetadataLookupBool(&status, recipe, "CONSTANT_PHOTOMETRIC_WEIGHTS");
+    if (!status) {
+        psAbort("You must provide a value for the BOOL recipe CONSTANT_PHOTOMETRIC_WEIGHTS");
+    }
+    int SKY_FIT_ORDER = psMetadataLookupS32(&status, recipe, "SKY_FIT_ORDER");
+    if (!status) {
+	SKY_FIT_ORDER = 0;
+    }
+    bool SKY_FIT_LINEAR = psMetadataLookupBool(&status, recipe, "SKY_FIT_LINEAR");
+    if (!status) {
+	SKY_FIT_LINEAR = false;
+    }
+
+    // construct source table with stand-alone fitSources
+    for (int i = 0; i < refSources->n; i++) {
+        pmSource *refSource = refSources->data[i];
+
+        // skip non-astronomical objects (very likely defects)
+        if (refSource->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (refSource->type == PM_SOURCE_TYPE_SATURATED) continue;
+        if (final) {
+            if (refSource->mode &  PM_SOURCE_MODE_SUBTRACTED) continue;
+        } else {
+            if (refSource->mode &  PM_SOURCE_MODE_BLEND) continue;
+        }
+
+	// which model to use?
+	pmModel *model = pmSourceGetModel (&isPSF, refSource);
+	if (model == NULL) continue;  // model must be defined
+
+        // save the original coords
+        x = model->params->data.F32[PM_PAR_XPOS];
+        y = model->params->data.F32[PM_PAR_YPOS];
+
+	// is the source in the region of interest?
+        if (x < AnalysisRegion.x0) continue;
+        if (y < AnalysisRegion.y0) continue;
+        if (x > AnalysisRegion.x1) continue;
+        if (y > AnalysisRegion.y1) continue;
+
+        pmSource *fitSource = pmSourceAlloc ();
+
+        // make temporary copies of the image pixels and mask
+	// we need to have a copy which will not be modified by changes to its neighbor
+        fitSource->mask   = psImageCopy (NULL, refSource->mask,   PS_TYPE_U8);
+        fitSource->pixels = psImageCopy (NULL, refSource->pixels, PS_TYPE_F32);
+        fitSource->weight = psImageCopy (NULL, refSource->weight, PS_TYPE_F32);
+
+        // set model to unit peak, zero sky (we assume sky is subtracted)
+	// XXX do this with the actual model or use keep the original guess?
+        model->params->data.F32[PM_PAR_SKY] = 0.0;
+        model->params->data.F32[PM_PAR_I0] = 1.0;
+
+        // fill in the model pixel values
+        psImageInit (fitSource->pixels, 0.0);
+        psImageKeepCircle (fitSource->mask, x, y, model->radiusFit, "OR", PM_MASK_MARK);
+        pmModelAdd (fitSource->pixels, fitSource->mask, model, false, false);
+
+        // save source in list
+	if (isPSF) {
+	  fitSource->modelPSF = psMemIncrRefCounter (model);
+	} else {
+	  fitSource->modelEXT = psMemIncrRefCounter (model);
+	}
+        index->data.U32[fitSources->n] = i;
+        psArrayAdd (fitSources, 100, fitSource);
+        psFree (fitSource);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built fitSources: %f (%ld objects)\n", psTimerMark ("psphot"), refSources->n);
+
+    // vectors to store stats for each object
+    // psVector *weight = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+    psVector *errors = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+
+    // create the border matrix (includes the sparse matrix)
+    // for just sky: 1 row; for x,y terms: 3 rows
+    psSparse *sparse = psSparseAlloc (fitSources->n, 100);
+    int nBorder = (SKY_FIT_ORDER == 0) ? 1 : 3;
+    psSparseBorder *border = psSparseBorderAlloc (sparse, nBorder);
+
+    // fill out the sparse matrix elements and border elements (B)
+    // Ri is the current refSource of interest (sci pixels)
+    // Fi is the current fitSource of interest (fit pixels)
+    // note that the sci pixels are real image pixels while the fit pixels are unique for each
+    // fitted source -- they hold the model flux
+
+    for (int i = 0; i < fitSources->n; i++) {
+        int N = index->data.U32[i];
+        pmSource *Ri = refSources->data[N];
+        pmSource *Fi = fitSources->data[i];
+
+        // diagonal elements of the sparse matrix (auto-cross-product)
+        f = pmSourceCrossProduct (Fi, Fi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+        psSparseMatrixElement (sparse, i, i, f);
+
+        // XXX being used? weight->data.F32[i] = r;
+
+	// the formal error depends on the weighting scheme
+	if (CONSTANT_PHOTOMETRIC_WEIGHTS) {
+	    float var = pmSourceCrossProduct (Fi, Fi, false);
+	    errors->data.F32[i] = 1.0 / sqrt(var);
+	} else {
+	    errors->data.F32[i] = 1.0 / sqrt(f);
+	}
+
+
+        // find the image x model value
+        f = pmSourceCrossProduct (Ri, Fi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+        psSparseVectorElement (sparse, i, f);
+
+	// add the per-source weights (border region)
+	switch (SKY_FIT_ORDER) {
+	  case 1:
+	    f = pmSourceWeight (Fi, 1, CONSTANT_PHOTOMETRIC_WEIGHTS);
+	    psSparseBorderElementB (border, i, 1, f);
+	    f = pmSourceWeight (Fi, 2, CONSTANT_PHOTOMETRIC_WEIGHTS);
+	    psSparseBorderElementB (border, i, 2, f);
+
+	  case 0:
+	    f = pmSourceWeight (Fi, 0, CONSTANT_PHOTOMETRIC_WEIGHTS);
+	    psSparseBorderElementB (border, i, 0, f);
+	    break;
+
+	  default:
+	    psAbort("Invalid SKY_FIT_ORDER %d\n", SKY_FIT_ORDER);
+	    break;
+	}
+
+        // loop over all other stars following this one
+        for (int j = i + 1; j < fitSources->n; j++) {
+            pmSource *Fj = fitSources->data[j];
+
+            // skip over disjoint source images, break after last possible overlap
+            if (Fi->pixels->row0 + Fi->pixels->numRows < Fj->pixels->row0) break;
+            if (Fj->pixels->row0 + Fj->pixels->numRows < Fi->pixels->row0) continue;
+            if (Fi->pixels->col0 + Fi->pixels->numCols < Fj->pixels->col0) continue;
+            if (Fj->pixels->col0 + Fj->pixels->numCols < Fi->pixels->col0) continue;
+
+            // got an overlap; calculate cross-product and add to output array
+            f = pmSourceCrossProduct (Fi, Fj, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseMatrixElement (sparse, j, i, f);
+        }
+    }
+
+    psSparseResort (sparse);
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built matrix: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    // set the sky, sky_x, sky_y components of border matrix
+    SetBorderT (border, readout, refSources, fitSources, index, CONSTANT_PHOTOMETRIC_WEIGHTS, SKY_FIT_ORDER);
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = 0.0;
+    constraint.paramMax   = 1e8;
+    constraint.paramDelta = 1e8;
+
+    // solve for normalization terms (need include local sky?)
+    psVector *norm = NULL;
+    psVector *skyfit = NULL;
+    if (SKY_FIT_LINEAR) {
+	psSparseBorderSolve (&norm, &skyfit, constraint, border, 5);
+	fprintf (stderr, "skyfit: %f\n", skyfit->data.F32[0]);
+    } else {
+	norm = psSparseSolve (NULL, constraint, sparse, 5);
+	skyfit = NULL;
+    }
+
+    // adjust fitSources, set refSources and subtract
+    for (int i = 0; i < fitSources->n; i++) {
+        int N = index->data.U32[i];
+        pmSource *Ri = refSources->data[N];
+	pmModel *model = pmSourceGetModel (NULL, Ri);
+
+        // assign linearly-fitted normalization
+        if (isnan(norm->data.F32[i])) {
+            psAbort("ensemble source is nan");
+        }
+        model->params->data.F32[PM_PAR_I0] = norm->data.F32[i];
+	// XXX note: is the value of 'errors' modified by the sky fit?
+        model->dparams->data.F32[PM_PAR_I0] = errors->data.F32[i];
+
+        // subtract object
+        pmModelSub (Ri->pixels, Ri->mask, model, false, false);
+        Ri->mode |= PM_SOURCE_MODE_SUBTRACTED;
+        if (!final) Ri->mode |= PM_SOURCE_MODE_TEMPSUB;
+	// XXX not sure about the use of TEMPSUB
+    }
+
+    // measure chisq for each source
+    for (int i = 0; final && (i < fitSources->n); i++) {
+        int N = index->data.U32[i];
+        pmSource *Ri = refSources->data[N];
+        pmModel *model = pmSourceGetModel (NULL, Ri);
+
+        x = model->params->data.F32[PM_PAR_XPOS];
+        y = model->params->data.F32[PM_PAR_YPOS];
+
+        psImageKeepCircle (Ri->mask, x, y, model->radiusFit, "OR", PM_MASK_MARK);
+        pmSourceChisq (model, Ri->pixels, Ri->mask, Ri->weight);
+        psImageKeepCircle (Ri->mask, x, y, model->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+    }
+
+    psFree (index);
+    psFree (sparse);
+    psFree (fitSources);
+    psFree (norm);
+    psFree (skyfit);
+    psFree (errors);
+    psFree (border);
+
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
+// calculate the weight terms for the sky fit component of the matrix
+static bool SetBorderT (psSparseBorder *border, pmReadout *readout, psArray *refSources, psArray *fitSources, psVector *index, bool constant_weights, int SKY_FIT_ORDER) {
+
+    // generate the image-wide weight terms
+    // turn on MARK for all image pixels
+    psRegion fullArray = psRegionSet (0, 0, 0, 0);
+    fullArray = psRegionForImage (readout->mask, fullArray);
+    psImageMaskRegion (readout->mask, fullArray, "OR", PM_MASK_MARK);
+
+    // turn off MARK for all object pixels
+    // we must use the refSources here since their pixels point at the science image
+    for (int i = 0; i < fitSources->n; i++) {
+        int N = index->data.U32[i];
+        pmSource *refSource = refSources->data[N];
+	pmModel *model = pmSourceGetModel (NULL, refSource);
+	if (model == NULL) continue;
+        float x = model->params->data.F32[PM_PAR_XPOS];
+        float y = model->params->data.F32[PM_PAR_YPOS];
+	psImageMaskCircle (refSource->mask, x, y, model->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+    }	
+
+    // accumulate the image statistics from the masked regions
+    psF32 **image = readout->image->data.F32;
+    psF32 **weight = readout->weight->data.F32;
+    psU8 **mask = readout->mask->data.U8;
+
+    double w, x, y, x2, xy, y2, xc, yc, wt, f, fo, fx, fy;
+    w = x = y = x2 = xy = y2 = fo = fx = fy = 0;
+    
+    int col0 = readout->image->col0;
+    int row0 = readout->image->row0;
+
+    for (int j = 0; j < readout->image->numRows; j++) {
+	for (int i = 0; i < readout->image->numCols; i++) {
+	    if (mask[j][i]) continue;
+	    if (constant_weights) {
+		wt = 1.0;
+	    } else {
+		wt = weight[j][i];
+	    }
+	    f = image[j][i];
+	    w   += 1/wt;
+	    fo  += f/wt;
+	    if (SKY_FIT_ORDER == 0) continue;
+
+	    xc = i + col0;
+	    yc = j + row0;
+	    x  +=    xc/wt;
+	    y  +=    yc/wt;
+	    x2 += xc*xc/wt;
+	    xy += xc*yc/wt;
+	    y2 += yc*yc/wt;
+	    fx +=  f*xc/wt;
+	    fy +=  f*yc/wt;
+	}
+    }
+
+    // turn off MARK for all image pixels
+    psImageMaskRegion (readout->mask, fullArray, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    // set the Border T elements
+    psSparseBorderElementG (border, 0, fo);
+    psSparseBorderElementT (border, 0, 0, w);
+    if (SKY_FIT_ORDER > 0) {
+	psSparseBorderElementG (border, 0, fx);
+	psSparseBorderElementG (border, 0, fy);
+	psSparseBorderElementT (border, 1, 0, x);
+	psSparseBorderElementT (border, 2, 0, y);
+	psSparseBorderElementT (border, 0, 1, x);
+	psSparseBorderElementT (border, 1, 1, x2);
+	psSparseBorderElementT (border, 2, 1, xy);
+	psSparseBorderElementT (border, 0, 2, y);
+	psSparseBorderElementT (border, 1, 2, xy);
+	psSparseBorderElementT (border, 2, 2, y2);
+    }    
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.c.in
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.c.in	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.c.in	(revision 21632)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PSPHOT_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "psphotErrorCodes.h"
+
+void psphotErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSPHOT_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSPHOT_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSPHOT_ERR_NERROR - PSPHOT_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.dat
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.dat	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.dat	(revision 21632)
@@ -0,0 +1,17 @@
+#
+# This file is used to generate psphotErrorClasses.h
+#
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+FITS			Problem in FITS I/O
+FITS_WCS		Error interpreting FITS WCS information
+PHOTOM			Problem in photometry
+PSF			Problem in PSF
+APERTURE		Problem with aperture photometry
+SKY			Problem in sky determination
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.h.in
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.h.in	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotErrorCodes.h.in	(revision 21632)
@@ -0,0 +1,18 @@
+#if !defined(PSPHOT_ERROR_CODES_H)
+#define PSPHOT_ERROR_CODES_H
+/*
+ * The line
+ *  PSPHOT_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSPHOT_ERR_BASE = 1300,
+    PSPHOT_ERR_${ErrorCode},
+    PSPHOT_ERR_NERROR
+} psphotErrorCode;
+
+void psphotErrorRegister(void);
+
+#endif
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEvalFLT.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEvalFLT.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEvalFLT.c	(revision 21632)
@@ -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_MODE_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_MODE_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_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is extended
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+
+    // the following source->mode information pertains to modelEXT:
+    source->mode |= PM_SOURCE_MODE_EXTMODEL;
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[1] <= 0) {
+	source->mode |= PM_SOURCE_MODE_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_MODE_POOR;
+    return false;
+}	
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEvalPSF.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEvalPSF.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotEvalPSF.c	(revision 21632)
@@ -0,0 +1,225 @@
+# 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[PM_PAR_SXX],dparams[PM_PAR_SXX] to have a scatter of:
+// sigma_x / (S/N)
+
+// any objects which is consistent with the PSF should have 
+// abs(dparams[PM_PAR_SXX]) < N * dsxLine(mag) & abs(dparams[PM_PAR_SYY]) < N * dsyLine(mag)
+// this includes a minimum buffer (DS) for the brighter objects
+
+// EAM : 2006.10.20 : I have re-defined params[PM_PAR_SXX] : it is now sqrt(2)*sigma_x, not 1/sigma_x
+
+// saturated stars should fall outside (larger), but have peaks above SATURATION
+// extended sources should be larger, cosmic rays smaller
+// we also reject objects with S/N too low or ChiSquare to high
+
+// floor for DS value 
+// XXX EAM : add to configuration?
+
+static float SATURATION;
+static float PSF_MIN_SN;
+static float PSF_MIN_DS;
+static float PSF_MAX_CHI;
+static float PSF_SHAPE_NSIGMA;
+static float PSF_DSX_MEAN;
+static float PSF_DSY_MEAN;
+static float PSF_DSX_STDEV;
+static float PSF_DSY_STDEV;
+
+bool psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout) {
+
+    bool status;
+
+    // XXX do we need to set this differently from the value used to mark saturated pixels?
+    pmCell *cell     = readout->parent;
+    SATURATION       = psMetadataLookupF32 (&status, cell->concepts, "CELL.SATURATION");
+    PSF_MIN_SN       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_SN");
+    PSF_MAX_CHI      = psMetadataLookupF32 (&status, recipe, "PSF_MAX_CHI");
+    PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_SHAPE_NSIGMA");
+    PSF_MIN_DS       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_DS");
+    if (!status) PSF_MIN_DS = 0.01;
+
+    PSF_DSX_MEAN     = psMetadataLookupF32 (&status, recipe, "DSX_MEAN");
+    PSF_DSY_MEAN     = psMetadataLookupF32 (&status, recipe, "DSY_MEAN");
+    PSF_DSX_STDEV    = psMetadataLookupF32 (&status, recipe, "DSX_STDV");
+    PSF_DSY_STDEV    = psMetadataLookupF32 (&status, recipe, "DSY_STDV");
+
+    return true;
+}
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalPSF (pmSource *source, pmModel *model) { 
+
+    int keep;
+    float dSX, dSY, SX, SY, SN;
+    float nSx, nSy, Chi;
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // 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_MODE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+
+    SN  = model->params->data.F32[PM_PAR_I0]/model->dparams->data.F32[PM_PAR_I0];
+    SX  = model->params->data.F32[PM_PAR_SXX];
+    SY  = model->params->data.F32[PM_PAR_SYY];
+    dSX = model->dparams->data.F32[PM_PAR_SXX];
+    dSY = model->dparams->data.F32[PM_PAR_SYY];
+    Chi = model->chisqNorm / model->nDOF;
+
+    // swing of sigma_x,y in sigmas
+    nSx = (dSX - PSF_DSX_MEAN) / (PSF_DSX_STDEV * PS_MAX (PSF_MIN_DS, (SX / SN)));
+    nSy = (dSY - PSF_DSY_MEAN) / (PSF_DSY_STDEV * PS_MAX (PSF_MIN_DS, (SY / SN)));
+
+    // assign PM_SOURCE_MODE_GOODSTAR to bright objects within PSF region of dparams[]
+    keep = TRUE;
+    keep &= (fabs(nSx) < PSF_SHAPE_NSIGMA);
+    keep &= (fabs(nSy) < PSF_SHAPE_NSIGMA);
+    keep &= (SN > PSF_MIN_SN);
+    keep &= (Chi < PSF_MAX_CHI);
+    if (keep) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psTrace ("psphot", 7, "PSFSTAR kept (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		     model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+	}
+	return true;
+    }
+
+    // this source is not a star, warn if it was a PSFSTAR
+    if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	psTrace ("psphot", 5, "PSFSTAR demoted based on fit quality   (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+    } else {
+	psTrace ("psphot", 5, "fails PSF fit (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+    }
+
+    // object appears to be small, suspected defect.  this is a weak indication for a defect: a
+    // large positive or negative swing can also come from the bright sources for which we are 
+    // more sensitive to the wings than for the faint sources.  
+    // XXX allow -NSIGMA and +NSIGMA to have different values?
+    if ((nSx <= -PSF_SHAPE_NSIGMA) || (nSy <= -PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_DEFECT;
+	return false;
+    }
+
+    // object appears to be large, suspected extended source
+    if ((nSx >= PSF_SHAPE_NSIGMA) || (nSy >= PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_EXTENDED;
+	return false;
+    }
+
+    // poor-quality fit; only keep if nothing else works...
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalDBL (pmSource *source, pmModel *model) { 
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // 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_MODE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+    return true;
+}	
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFakeSources.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFakeSources.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFakeSources.c	(revision 21632)
@@ -0,0 +1,30 @@
+# include "psphot.h"
+
+psArray *psphotFakeSources () {
+
+    // psphotUpdateHeader (header, config);
+
+    psArray *sources = psArrayAlloc (50);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = pmSourceAlloc ();
+        source->moments = pmMomentsAlloc ();
+        source->moments->x = 10;
+        source->moments->y = 10;
+        source->moments->Sx = 1;
+        source->moments->Sy = 1;
+        source->moments->Sxy = 0;
+        source->moments->Sum = 1000;
+        source->moments->Peak = 100;
+        source->moments->Sky = 10;
+        source->moments->nPixels = 10;
+
+        source->peak = pmPeakAlloc (10, 10, 0, 0);
+        source->type = PM_SOURCE_TYPE_STAR;
+
+        pmModelType modelType = pmModelSetType ("PS_MODEL_QGAUSS");
+        source->modelPSF = pmSourceModelGuess (source, modelType);
+        sources->data[i] = source;
+    }
+    return sources;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFindPeaks.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFindPeaks.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFindPeaks.c	(revision 21632)
@@ -0,0 +1,117 @@
+# include "psphot.h"
+
+// In this function, we smooth the image, then search for the peaks 
+psArray *psphotFindPeaks (pmReadout *readout, psMetadata *recipe, int pass) {
+
+    float SIGMA_SMTH, NSIGMA_SMTH, NSIGMA_PEAK;
+    bool  status = false;
+
+    // smooth the image and weight map
+    psTimerStart ("psphot");
+
+    if (pass == 1) {
+	SIGMA_SMTH  = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_SIGMA");
+	NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+    } else {
+	bool status_x, status_y;
+	float FWHM_X = psMetadataLookupF32 (&status_x, recipe, "FWHM_X");
+	float FWHM_Y = psMetadataLookupF32 (&status_y, recipe, "FWHM_Y");
+	if (!status_x | !status_y) {
+	    psError(PSPHOT_ERR_CONFIG, false, "FWHM_X or FWHM_Y not defined");
+	    return false;
+	}
+	SIGMA_SMTH  = 0.5*(FWHM_X + FWHM_Y) / (2.0*sqrt(2.0*log(2.0)));
+	NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+    }
+
+    // smooth the image, applying the mask as we go
+    psImage *smooth_im = psImageCopy (NULL, readout->image, PS_TYPE_F32);
+    psImageSmoothMaskF32 (smooth_im, readout->mask, 0xff, SIGMA_SMTH, NSIGMA_SMTH);
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "smooth image: %f sec\n", psTimerMark ("psphot"));
+
+    // smooth the weight, applying the mask as we go
+    psImage *smooth_wt = psImageCopy (NULL, readout->weight, PS_TYPE_F32);
+    psImageSmoothMaskF32 (smooth_wt, readout->mask, 0xff, SIGMA_SMTH/sqrt(2), NSIGMA_SMTH);
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "smooth weight: %f sec\n", psTimerMark ("psphot"));
+
+    psImage *mask = readout->mask;
+
+    // optionally save example images under trace 
+    if (psTraceGetLevel("psphot") > 5) {
+	psphotSaveImage (NULL, smooth_im, "imsmooth.fits");
+	psphotSaveImage (NULL, smooth_wt, "wtsmooth.fits");
+    }
+
+    // build the significance image on top of smooth_im
+    for (int j = 0; j < smooth_im->numRows; j++) {
+	for (int i = 0; i < smooth_im->numCols; i++) {
+	    float value = smooth_im->data.F32[j][i];
+	    if (value < 0 || smooth_wt->data.F32[j][i] <= 0 || mask->data.U8[j][i]) {
+		smooth_im->data.F32[j][i] = 0.0;
+	    } else {
+		smooth_im->data.F32[j][i] = PS_SQR(value) / smooth_wt->data.F32[j][i];
+	    }
+	}
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "built smoothed signficance image: %f sec\n", psTimerMark ("psphot"));
+
+    // optionally save example images under trace 
+    if (psTraceGetLevel("psphot") > 5) {
+	psphotSaveImage (NULL, smooth_im, "snsmooth.fits");
+    }
+
+    psTimerStart ("psphot");
+    // set peak threshold
+
+    // signal/noise limit for the detected peaks
+    if (pass == 1) {
+	NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT");
+    } else {
+	NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT_2");
+    }	
+    
+    // we need to define the threshold based on the value of NSIGMA_PEAK and the applied smoothing
+    // gaussian SIGMA.  a peak in the significance image has an effective S/N for faint sources
+    // of (S = Io*2pi*sigma_eff^2) / sqrt(Var), where sigma_eff^2 = sigma_obs^2 + sigma_sm^2.
+    // the smoothing of the varience map does not affect the faint-source S/N since the noise
+    // is constant under a faint source.
+
+    // if sigma_sm = sigma_obs, then sigma_eff^2 = 2 sigma_sm^2. in this case, the threshold in
+    // terms of smooth_im peak counts Io, for a desired S/N limit corresponds to
+    // S/N = sqrt(Io)*4*pi*sigma_sm^2
+    // thus, the threshold is: 
+    float effArea = 4.0*M_PI*PS_SQR(SIGMA_SMTH);
+    float threshold = PS_SQR(NSIGMA_PEAK) / effArea;
+
+    // find the peaks in the smoothed image
+    psArray *peaks = pmFindImagePeaks (smooth_im, threshold);
+    if (peaks == NULL) {
+	// XXX this may also be due to a programming or config error
+	// XXX do we need to set something in the readout->analysis to indicate that 
+	// we tried and failed to find peaks (something in the header data)
+	psError(PSPHOT_ERR_DATA, false, "no peaks found in this image");
+	return false;
+    }
+    
+    // correct the peak values to S/N = sqrt(value*effArea)
+    // get the peak flux from the unsmoothed image
+    // the peak pixel coords are guaranteed to be on the image
+    for (int i = 0; i < peaks->n; i++) {
+	pmPeak *peak = peaks->data[i];
+	peak->SN = sqrt(peak->value*effArea);
+	peak->flux = readout->image->data.F32[peak->y][peak->x];
+    }
+
+    psFree (smooth_im);
+    psFree (smooth_wt);
+
+    // optional dump of all peak data
+    char *output = psMetadataLookupStr (&status, recipe, "PEAKS_OUTPUT_FILE");
+    if (status && (output != NULL) && (output[0])) {
+	pmPeaksWriteText (peaks, output);
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks: %f sec\n", peaks->n, psTimerMark ("psphot"));
+
+    return (peaks);
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFitSet.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFitSet.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotFitSet.c	(revision 21632)
@@ -0,0 +1,47 @@
+# include "psphot.h"
+
+// XXX this is not used in main psphot code
+bool psphotFitSet (pmSource *source, pmModel *oneModel, char *fitset, pmSourceFitMode mode) {
+
+    double x, y, Io;
+
+    FILE *f = fopen (fitset, "r");
+    if (f == NULL) return false;
+
+    psArray *modelSet = psArrayAllocEmpty (16);
+
+    while (fscanf (f, "%lf %lf %lf", &x, &y, &Io) == 3) {
+        pmModel *model = pmModelAlloc (oneModel->type);
+
+        for (psS32 i = 0; i < model->params->n; i++) {
+            model->params->data.F32[i] = oneModel->params->data.F32[i];
+            model->dparams->data.F32[i] = oneModel->dparams->data.F32[i];
+        }
+        model->params->data.F32[1] = Io;
+        model->params->data.F32[2] = x;
+        model->params->data.F32[3] = y;
+        psArrayAdd (modelSet, 16, model);
+    }
+
+    pmSourceFitSet (source, modelSet, mode);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    for (int i = 0; i < modelSet->n; i++) {
+        pmModel *model = modelSet->data[i];
+        pmModelSub (source->pixels, source->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;
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotGrowthCurve.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotGrowthCurve.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotGrowthCurve.c	(revision 21632)
@@ -0,0 +1,80 @@
+# 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
+
+// XXX add a option to turn off the curve-of-growth (ie, make the apMag = fitMag everywhere);
+
+bool psphotGrowthCurve (pmReadout *readout, pmPSF *psf, bool ignore) {
+
+    // bool status;
+    float xc, yc, dx, dy;
+    float fitMag, apMag;
+    float radius;
+
+    // create template model
+    pmModel *modelRef = pmModelAlloc(psf->type);
+
+    // use the center of the center pixel of the image
+    xc = 0.5*readout->image->numCols + readout->image->col0 + 0.5;
+    yc = 0.5*readout->image->numRows + readout->image->row0 + 0.5;
+    dx = psf->growth->maxRadius + 1;
+    dy = psf->growth->maxRadius + 1;
+
+    // assign the x and y coords to the image center
+    // create an object with center intensity of 1000
+    modelRef->params->data.F32[PM_PAR_SKY] = 0;
+    modelRef->params->data.F32[PM_PAR_I0] = 1000;
+    modelRef->params->data.F32[PM_PAR_XPOS] = xc;
+    modelRef->params->data.F32[PM_PAR_YPOS] = yc;
+
+    // create modelPSF from this model
+    pmModel *model = pmModelFromPSF (modelRef, psf);
+
+    // measure the fitMag for this model
+    pmSourcePhotometryModel (&fitMag, model);
+    psf->growth->fitMag = fitMag;
+
+    // generate working image for this source
+    psRegion region = {xc - dx, xc + dx, yc - dy, yc + dy};
+    psImage *view = psImageSubset (readout->image, region);
+    psImage *image = psImageCopy (NULL, view, PS_TYPE_F32);
+    psImage *mask = psImageCopy (NULL, view, PS_TYPE_U8);
+    psImageInit (mask, 0);
+
+    // loop over a range of source fluxes
+    // no need to interpolate since we have force the object center 
+    // to 0.5, 0.5 above
+    for (int i = 0; i < psf->growth->radius->n; i++) {
+
+        psImageInit (image, 0.0);
+
+        radius = psf->growth->radius->data.F32[i];
+
+        psImageKeepCircle (mask, xc, yc, radius, "OR", PM_MASK_MARK);
+        pmModelAdd (image, mask, model, false, false);
+        pmSourcePhotometryAper (&apMag, model, image, mask);
+        psImageKeepCircle (mask, xc, yc, radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+	// the 'ignore' mode is for testing
+	if (ignore) {
+	    psf->growth->apMag->data.F32[i] = fitMag;
+	} else {
+	    psf->growth->apMag->data.F32[i] = apMag;
+	}
+    }
+
+    psf->growth->apRef = psVectorInterpolate (psf->growth->radius, psf->growth->apMag, psf->growth->refRadius);
+    psf->growth->apLoss = psf->growth->fitMag - psf->growth->apRef;
+
+    psLogMsg ("psphot.growth", 4, "GrowthCurve : apLoss : %f\n", psf->growth->apLoss);
+
+    psFree (view);
+    psFree (image);
+    psFree (mask);
+    psFree (model);
+    psFree (modelRef);
+
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotGuessModels.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotGuessModels.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotGuessModels.c	(revision 21632)
@@ -0,0 +1,62 @@
+# include "psphot.h"
+
+// construct an initial PSF model for each object 
+bool psphotGuessModels (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+  psTimerStart ("psphot");
+
+  // setup the PSF fit radius details
+  psphotInitRadiusPSF (recipe, psf->type);
+
+  for (int i = 0; i < sources->n; i++) {
+    pmSource *source = sources->data[i];
+
+    // skip non-astronomical objects (very likely defects)
+    if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+    if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+    // XXX if a source is faint, it will not have moments measured.
+    // it must be modelled as a PSF.  In this case, we need to use 
+    // the peak centroid to get the coordinates and get the peak flux 
+    // from the image?
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *modelEXT = pmSourceModelGuess (source, psf->type);
+    
+    // XXX put this in a function of its own..
+    if (modelEXT == NULL) {
+	psErrorClear (); // XXX need to clear the error from failing the model
+	modelEXT = pmModelAlloc(psf->type);
+	psF32 *PAR = modelEXT->params->data.F32;
+	PAR[PM_PAR_SKY]  = 0;
+	// XXX get this from the image pixels
+	PAR[PM_PAR_I0]   = source->peak->flux;
+	PAR[PM_PAR_XPOS] = source->peak->xf;
+	PAR[PM_PAR_YPOS] = source->peak->yf;
+    } else {
+	// these valuse are set in pmSourceModelGuess, should this rule be in there as well?
+	if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+	    modelEXT->params->data.F32[PM_PAR_XPOS] = source->moments->x;
+	    modelEXT->params->data.F32[PM_PAR_YPOS] = source->moments->y;
+	} else {
+	    modelEXT->params->data.F32[PM_PAR_XPOS] = source->peak->xf;
+	    modelEXT->params->data.F32[PM_PAR_YPOS] = source->peak->yf;
+	}
+    }
+
+    // set PSF parameters for this model (apply 2D shape model)
+    pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+    psFree (modelEXT);
+
+    // XXX need to define the guess flux?
+    // set the fit radius based on the object flux limit and the model
+    psphotCheckRadiusPSF (readout, source, modelPSF);
+
+    // set the source PSF model
+    source->modelPSF = modelPSF;
+  }
+  psLogMsg ("psphot.models", 4, "built models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+  return true;
+}
+
+// XXX do we always know which model is supposed to be used?  
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotImageLoop.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotImageLoop.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotImageLoop.c	(revision 21632)
@@ -0,0 +1,102 @@
+# include "psphot.h"
+
+// XXX the errors in the pmFPAfileIOChecks could also be due to a programming or config error
+// XXX we need to either handle those errors or handle the error in pmFPAfileIOChecks and exit
+bool psphotImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+	psError(PSPHOT_ERR_DATA, false, "failed IO for fpa in psphot\n");
+	psFree(view);
+	return false;
+    }
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (! chip->process || ! chip->file_exists) { continue; }
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+            psError(PSPHOT_ERR_DATA, false, "failed IO for chip %d in psphot\n", view->chip);
+	    psFree (view);
+	    return false;
+	}
+	
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (! cell->process || ! cell->file_exists) { continue; }
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+		psError(PSPHOT_ERR_DATA, false, "failed IO for chip %d, cell %d in psphot\n", view->chip, view->cell);
+		psFree (view);
+		return false;
+	    }
+	    
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+		    psError(PSPHOT_ERR_DATA, false, "failed IO for chip %d, cell %d, readout %d in psphot\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+		
+		if (! readout->data_exists) { continue; }
+		
+		// run the actual photometry analysis
+		if (!psphotReadout (config, view)) {
+		    psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+		    psError(PSPHOT_ERR_DATA, false, "failed IO for chip %d, cell %d, readout %d in psphot\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+	    }
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+		psError(PSPHOT_ERR_DATA, false, "failed IO for chip %d, cell %d in psphot\n", view->chip, view->cell);
+		psFree (view);
+		return false;
+	    }
+	}
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+            psError(PSPHOT_ERR_DATA, false, "failed IO for chip %d in psphot\n", view->chip);
+	    psFree (view);
+	    return false;
+	}
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+	psError(PSPHOT_ERR_DATA, false, "failed IO for fpa in psphot\n");
+	psFree (view);
+	return false;
+    }
+
+    psFree (view);
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) return false;
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotImageMedian.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotImageMedian.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotImageMedian.c	(revision 21632)
@@ -0,0 +1,309 @@
+# include "psphot.h"
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotImageMedian (pmConfig *config, pmFPAview *view)
+{
+    bool status;
+    pmFPA *inFPA;
+    pmFPAfile *file;
+    psRegion region;
+    static char *defaultStatsName = "FITTED_MEAN";
+
+    pmReadout *model = NULL;
+    pmReadout *readout = NULL;
+    pmReadout *background = NULL;
+    pmReadout *backSub = NULL;
+
+    psTimerStart ("psphot");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    // user supplied seed, if available
+    unsigned long seed = psMetadataLookupS32 (&status, recipe, "IMSTATS_SEED");
+    if (!status) {
+        seed = 0;
+    }
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, seed);
+
+    // subtract this amount extra from the sky
+    float SKY_BIAS = psMetadataLookupF32 (&status, recipe, "SKY_BIAS");
+    if (!status) {
+        SKY_BIAS = 0;
+    }
+
+    // supply the sky background statistics options
+    char *statsName = psMetadataLookupStr (&status, recipe, "SKY_STAT");
+    if (statsName == NULL) {
+	statsName = defaultStatsName;
+    }
+    psStatsOptions statsOption = psStatsOptionFromString (statsName);
+    if (!(statsOption & (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN | PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_QUARTILE | PS_STAT_CLIPPED_MEAN | PS_STAT_FITTED_MEAN | PS_STAT_FITTED_MEAN_V2 | PS_STAT_FITTED_MEAN_V3))) {
+	statsOption = PS_STAT_FITTED_MEAN;
+    }
+    psStats *stats = psStatsAlloc (statsOption);
+
+    // set range for old-version of sky statistic
+    if (statsOption & PS_STAT_ROBUST_QUARTILE) {
+	stats->min = 0.25;
+	stats->max = 0.75;
+    }
+
+    // set user-option for number of pixels per region
+    stats->nSubsample = psMetadataLookupF32 (&status, recipe, "IMSTATS_NPIX");
+    if (!status) {
+        stats->nSubsample = 1000;
+    }
+
+    // optionally set the binsize
+    stats->binsize = psMetadataLookupF32 (&status, recipe, "SKY_HISTOGRAM_BINS");
+    if (status) {
+        stats->options |= PS_STAT_USE_BINSIZE;
+    }
+
+    // find the currently selected readout
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    inFPA = file->fpa;
+    readout = pmFPAviewThisReadout (view, inFPA);
+
+    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, recipe, "BACKGROUND.XBIN");
+    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    // overhang : we will balance this evenly
+    int xExtra = (Nx % DX) / 2;
+    int yExtra = (Ny % DY) / 2;
+    int xOffset = (xExtra > 0) ? DX - xExtra : 0;
+    int yOffset = (yExtra > 0) ? DY - yExtra : 0;
+    xOffset -= image->col0;
+    yOffset -= image->row0;
+
+    // dimensions of binned image
+    int nx, ny;
+    if (Nx % DX == 0) {
+	nx = Nx / DX;
+    } else {
+	nx = (xExtra) ? (Nx / DX) + 2 : (Nx / DX) + 1;
+    }
+    if (Ny % DY == 0) {
+	ny = Ny / DY;
+    } else {
+	ny = (yExtra) ? (Ny / DY) + 2 : (Ny / DY) + 1;
+    }
+
+    // we have 4 possibilities: (INTERNAL or I/O file) and (exists or not) 
+    // select model pixels (from output background model file, or create internal file)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKMDL");
+    if (file == NULL) {
+	// we are not using PSPHOT.BACKMDL as an I/O file: define an internal version
+        model = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKMDL", nx, ny, PS_TYPE_F32);
+    } else {
+	if (file->mode == PM_FPA_MODE_INTERNAL) {
+	    model = file->readout;
+	} else {
+	    // we are using PSPHOT.BACKMDL as an I/O file: select readout or create
+	    model = pmFPAviewThisReadout (view, file->fpa);
+	    if (model == NULL) {
+		// readout does not yet exist: create from input
+		pmFPAfileCopyStructureView (file->fpa, inFPA, DX, DY, view);
+		model = pmFPAviewThisReadout (view, file->fpa);
+		if ((nx != model->image->numCols) || (ny != model->image->numRows)) {
+		    psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for model dimensions");
+		    return false;
+		}
+	    }
+	}
+    }
+    psF32 **modelData = model->image->data.F32;
+
+    assert(model->analysis != NULL);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.XBIN", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background x-binsize", DX);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.YBIN", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background x-binsize", DY);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.XOFF", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background x-overhang", xOffset);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.YOFF", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background y-overhang", yOffset);
+
+
+    // 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 - xOffset;
+            int sy = iy*DY - yOffset;
+            region = psRegionSet (sx, sx + 2*DX, sy, sy + 2*DY);
+            region = psRegionForImage (image, region);
+            psImage *subset  = psImageSubset (image, region);
+	    if (!subset->numCols || !subset->numRows) {
+		psFree (subset);
+		continue;
+	    }
+
+            psImage *submask = psImageSubset (mask, region);
+
+            // Use the selected background statistic for the first pass
+	    // If it fails, fall back on the "ROBUST_MEDIAN" version
+	    // If both fail, set the pixel to NAN and (below) interpolate
+	    // XXX psImageBackground will probably be renamed psImageStats
+            if (psImageBackground(stats, subset, submask, 0xff, rng)) {
+		if (stats->options & PS_STAT_ROBUST_QUARTILE) {
+		    modelData[iy][ix] = stats->robustMedian;
+		} else {	
+		    modelData[iy][ix] = psStatsGetValue(stats, statsOption);
+		}
+	    } else {
+		psStatsOptions currentOptions = stats->options;
+		stats->options |= PS_STAT_ROBUST_MEDIAN;
+		if (!psImageBackground(stats, subset, submask, 0xff, rng)) {
+		    psLogMsg ("psphot", PS_LOG_WARN, "Failed to estimate background using ROBUST_MEDIAN for "
+			       "(%dx%d, (row0,col0) = (%d,%d)",
+			       subset->numRows, subset->numCols, subset->row0, subset->col0);
+		    modelData[iy][ix] = NAN;
+		} else {
+		    modelData[iy][ix] = psStatsGetValue (stats, PS_STAT_ROBUST_MEDIAN);
+		}
+		// drop errors caused by psImageBackground failures
+		// XXX we probably should trap and exit on serious failures
+		psErrorClear(); 
+		stats->options = currentOptions;
+	    }
+	    modelData[iy][ix] += SKY_BIAS;
+            psFree (subset);
+            psFree (submask);
+        }
+    }
+
+    // patch over bad regions (use average of 8 possible neighbor pixels)
+    // XXX consider testing pixels against the 8 neighbors and replacing outliers...
+    float Count = 0;
+    float Value = 0;
+    for (int iy = 0; iy < ny; iy++) {
+        for (int ix = 0; ix < nx; ix++) {
+	    if (!isnan(modelData[iy][ix])) {
+		Value += modelData[iy][ix];
+		Count += 1;
+		continue;
+	    }
+	    float value = 0;
+	    float count = 0;
+	    for (int jy = iy - 1; jy <= iy + 1; jy++) {
+		if (jy <   0) continue;
+		if (jy >= ny) continue;
+		for (int jx = ix - 1; jx <= ix + 1; jx++) {
+		    if (!jx && !jy) continue;
+		    if (jx   <   0) continue;
+		    if (jx   >= nx) continue;
+		    value += modelData[jy][jx];
+		    count += 1.0;
+		}
+	    }
+	    if (count > 0) modelData[iy][ix] = value / count;
+	    
+	}
+    }
+    Value = Value / Count;
+
+    // patch over remaining bad regions (use global average)
+    for (int iy = 0; iy < ny; iy++) {
+        for (int ix = 0; ix < nx; ix++) {
+	    if (!isnan(modelData[iy][ix])) continue;
+	    modelData[iy][ix] = Value;
+	}
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build median image: %f sec\n", psTimerMark ("psphot"));
+
+    // measure background stats and save for later output
+    psStats *statsBck = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV | PS_STAT_MIN | PS_STAT_MAX);
+    psImageStats (statsBck, model->image, NULL, 0);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MEAN", PS_DATA_F32 | PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYSTDEV", PS_DATA_F32 | PS_META_REPLACE, "sky model stdev",        statsBck->sampleStdev);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MAX",  PS_DATA_F32 | PS_META_REPLACE, "sky model maximum value", statsBck->max);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MIN",  PS_DATA_F32 | PS_META_REPLACE, "sky model minimum value", statsBck->min);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_NX",   PS_DATA_S32 | PS_META_REPLACE, "sky model size (x)",      nx);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_NY",   PS_DATA_S32 | PS_META_REPLACE, "sky model size (y)",      ny);
+    psLogMsg ("psphot", PS_LOG_INFO, "background sky : min %f mean %f max %f stdev %f", 
+	      statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
+    psFree (statsBck);
+
+    // select background pixels, from output background file, or create
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (file) {
+	// we are using PSPHOT.BACKGND as an I/O file: select readout or create
+	if (file->mode == PM_FPA_MODE_INTERNAL) {
+	    background = file->readout;
+	} else {
+	    background = pmFPAviewThisReadout (view, file->fpa);
+	}
+	if (background == NULL) {
+	    // readout does not yet exist: create from input
+	    pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+	    background = pmFPAviewThisReadout (view, file->fpa);
+	    if ((Nx != background->image->numCols) || (Ny != background->image->numRows)) {
+		psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for background dimensions");
+		return false;
+	    }
+	}
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", Nx, Ny, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    if (!psImageUnbin (background->image, model->image, DX, DY, xOffset, yOffset)) {
+	psError (PSPHOT_ERR_PROG, true, "failed to build background iamge");
+	return false;
+    }
+	
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build resampled image: %f sec\n", psTimerMark ("psphot"));
+
+    // back-sub image pixels, from output background file (don't create if not requested)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
+    if (file) {
+	// we are using PSPHOT.BACKSUB as an I/O file: select readout or create
+	backSub = pmFPAviewThisReadout (view, file->fpa);
+	if (backSub == NULL) {
+	    // readout does not yet exist: create from input
+	    pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+	    backSub = pmFPAviewThisReadout (view, file->fpa);
+	}
+    }
+
+# ifdef TESTSAVE
+    psphotSaveImage (NULL, image, "image.fits");
+    psphotSaveImage (NULL, background->image, "back.fits");
+    psphotSaveImage (NULL, mask, "mask.fits");
+    psphotSaveImage (NULL, model->image, "backmdl.fits");
+# endif
+
+    // subtract the background model (save in backSub, if requested)
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+	    image->data.F32[j][i] -= backData[j][i];
+	    if (backSub) {
+		backSub->image->data.F32[j][i] = image->data.F32[j][i];
+	    }
+        }
+    }
+
+# ifdef TESTSAVE
+    psphotSaveImage (NULL, image, "backsub.fits");
+# endif
+
+    psLogMsg ("psphot", PS_LOG_INFO, "subtracted background model: %f sec\n", psTimerMark ("psphot"));
+    psFree(stats);
+    psFree(rng);
+
+    // the pmReadout selected in this function are all view on entries in config->files
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMagnitudes.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMagnitudes.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMagnitudes.c	(revision 21632)
@@ -0,0 +1,55 @@
+# include "psphot.h"
+
+bool psphotMagnitudes(psArray *sources,
+		      psMetadata *recipe,
+		      pmPSF *psf,
+		      pmReadout *background)
+{
+    bool status = false;
+    int Nap = 0;
+
+    psTimerStart ("psphot");
+
+    pmSourceMagnitudesInit (recipe);
+
+    // XXX require (assert) that we have a background model, or 
+    // allow it to be missing, setting local sky to 0.0?
+
+    // Get enough information to return sky level
+    assert(background != NULL);
+    assert(background->analysis != NULL);
+    int DX = psMetadataLookupS32(&status, background->analysis, "PSPHOT.BACKGROUND.XBIN");
+    assert (status == true);
+    int DY = psMetadataLookupS32(&status, background->analysis, "PSPHOT.BACKGROUND.YBIN");
+    assert (status == true);
+    int dx = psMetadataLookupS32(&status, background->analysis, "PSPHOT.BACKGROUND.XOFF");
+    assert (status == true);
+    int dy = psMetadataLookupS32(&status, background->analysis, "PSPHOT.BACKGROUND.YOFF");
+    assert (status == true);
+
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_APCORR | PM_SOURCE_PHOT_WEIGHT;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	status = pmSourceMagnitudes (source, psf, photMode);
+	if (status) Nap ++;
+
+	source->sky = psImageUnbinPixel(source->peak->x, source->peak->y, background->image, DX, DY, dx, dy);
+	if (isnan(source->sky) && false) {
+	  psError(PSPHOT_ERR_SKY, false, "Setting pmSource.sky");
+	  psErrorStackPrint(NULL, " ");
+	  psErrorClear();
+	}
+    }	
+
+    psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "measure magnitudes : %f sec for %ld objects (%d with apertures)\n", psTimerMark ("psphot"), sources->n, Nap);
+    return true;
+}
+
+// XXX add in a measurement of the bright and faint completeness values
+// XXX push these into the recipe
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMaskReadout.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMaskReadout.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMaskReadout.c	(revision 21632)
@@ -0,0 +1,24 @@
+# include "psphot.h"
+
+bool psphotMaskReadout (pmReadout *readout, 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);
+
+    // restrict the supplied region above to the valid area on the image
+    psRegion keep = psRegionForImage (readout->image, valid);
+
+    // psImageKeepRegion assumes the region refers to the parent coordinates
+    psImageKeepRegion (readout->mask, keep, "OR", PM_MASK_BAD);
+
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMergeSources.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMergeSources.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotMergeSources.c	(revision 21632)
@@ -0,0 +1,52 @@
+# include "psphot.h"
+
+// add newly selected sources to the existing list of sources
+bool psphotMergeSources (psArray *oldSources, psArray *newSources) {
+
+    for (int i = 0; i < newSources->n; i++) {
+	pmSource *source = newSources->data[i];
+	psArrayAdd (oldSources, 100, source);
+    }
+    return true;
+}
+
+// merge the externally supplied sources with the existing sources.  mark them as having 
+// mode PM_SOURCE_MODE_EXTERNAL
+bool psphotLoadExtSources (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.SRC");
+    if (!readout) {
+	psLogMsg ("psphot", 3, "no external sources supplied");
+	return true;
+    }
+
+    psArray *extSources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (!extSources) {
+	psLogMsg ("psphot", 3, "no external sources for this readout");
+	return true;
+    }
+
+    for (int i = 0; i < extSources->n; i++) {
+	pmSource *source = extSources->data[i];
+	source->mode |= PM_SOURCE_MODE_EXTERNAL;
+	pmModel *model = source->modelPSF;
+
+	float xpos = model->params->data.F32[PM_PAR_XPOS];
+	float ypos = model->params->data.F32[PM_PAR_YPOS];
+
+	source->peak = pmPeakAlloc(xpos, ypos, 1.0, PM_PEAK_LONE);
+	source->peak->xf = xpos;
+	source->peak->yf = ypos;
+	source->peak->flux = 1.0;
+	
+	// drop the loaded source modelPSF
+	psFree (source->modelPSF);
+	source->modelPSF = NULL;
+    }
+
+    psphotMergeSources (sources, extSources);
+    psLogMsg ("psphot", 3, "%ld external sources merged to yield %ld total sources", extSources->n, sources->n);
+
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotModelGroupInit.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotModelGroupInit.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotModelGroupInit.c	(revision 21632)
@@ -0,0 +1,25 @@
+# include "psphot.h"
+
+// Add locally-defined models here.  As these mature, they can be moved to 
+// psModule/src/objects/models
+
+# include "models/pmModel_TEST1.c"
+# include "models/pmModel_STRAIL.c"
+
+static pmModelGroup userModels[] = {
+    {"PS_MODEL_TEST1", 7, pmModelFunc_TEST1,  pmModelFlux_TEST1,  pmModelRadius_TEST1,  pmModelLimits_TEST1,  pmModelGuess_TEST1, pmModelFromPSF_TEST1, pmModelFitStatus_TEST1},
+    {"PS_MODEL_STRAIL", 9, pmModelFunc_STRAIL,  pmModelFlux_STRAIL,  pmModelRadius_STRAIL,  pmModelLimits_STRAIL,  pmModelGuess_STRAIL, pmModelFromPSF_STRAIL, pmModelFitStatus_STRAIL},
+};
+
+void psphotModelGroupInit (void) 
+{ 
+
+    // if pmModelGroupInit returns false, we have already init'ed
+    if (!pmModelGroupInit ()) return;
+
+    int Nmodels = sizeof (userModels) / sizeof (pmModelGroup);
+    for (int i = 0; i < Nmodels; i++) {
+	pmModelGroupAdd (&userModels[i]);
+    }
+    return;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotModelTest.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotModelTest.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotModelTest.c	(revision 21632)
@@ -0,0 +1,191 @@
+# include "psphot.h"
+static char DEFAULT_MODE[] = "EXT";
+
+bool psphotModelTest (pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+    int modelType;
+    unsigned int Nfail;
+    float obsMag, fitMag, value;
+    char name[64];
+    pmPSF *psf = NULL;
+    pmSourceFitMode fitMode;
+
+    psMetadataItem *item  = NULL;
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) POISSON_ERRORS = true;
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // run model fitting tests on a single source
+    if (!psMetadataLookupBool (&status, recipe, "TEST_FIT")) return false;
+
+    // what fitting mode to use?
+    char *fitModeWord = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODE");
+    if (!status) {
+        fitModeWord = DEFAULT_MODE;
+    }
+    fitMode = PM_SOURCE_FIT_EXT;
+    if (!strcasecmp (fitModeWord, "PSF")) fitMode = PM_SOURCE_FIT_PSF;
+
+    // in fitMode, psf sets the model type
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+        char *psfFile = psMetadataLookupStr (&status, recipe, "PSF_INPUT_FILE");
+        if (!status) psAbort("PSF_INPUT_FILE not supplied");
+        psMetadata *psfData = psMetadataConfigRead(NULL, &Nfail, psfFile, FALSE);
+        psf = pmPSFfromMetadata (psfData);
+        modelType = psf->type;
+    } else {
+        // find the model: supplied by user or first in the PSF_MODEL list
+        char *modelName  = psMetadataLookupStr (&status, recipe, "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("missing PSF_MODEL selection");
+            if (mdi->type == PS_DATA_STRING) {
+                list = psListAlloc(NULL);
+                psListAdd (list, PS_LIST_HEAD, mdi);
+            } else {
+                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+                list = psMemIncrRefCounter(mdi->data.list);
+            }
+
+            // take the first list element
+            item = psListGet (list, PS_LIST_HEAD);
+            modelName = item->data.V;
+        }
+        modelType = pmModelSetType (modelName);
+        if (modelType < 0) psAbort("unknown model %s", modelName);
+    }
+
+    // find the fitting parameters (try test values first)
+    float INNER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_INNER_RADIUS");
+    if (!status) {
+        INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    }
+
+    float OUTER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_OUTER_RADIUS");
+    if (!status) {
+        OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    }
+
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "TEST_FIT_RADIUS");
+    if (!status) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    }
+
+    float mRADIUS = psMetadataLookupF32 (&status, recipe, "TEST_MOMENTS_RADIUS");
+    if (!status) {
+        mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // define the source of interest
+    float xObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_X");
+    float yObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_Y");
+
+    // construct the source structures
+    pmSource *source = pmSourceAlloc();
+    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
+    pmSourceDefinePixels (source, readout, xObj, yObj, OUTER);
+
+    // find the local sky
+    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER);
+    if (!status) psAbort("pmSourceLocalSky error");
+
+    // get the source moments
+    status = pmSourceMoments (source, mRADIUS);
+    if (!status) psAbort("pmSourceLocalSky error");
+    source->peak->value = source->moments->Peak;
+
+    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->x, source->moments->y);
+
+    psEllipseMoments moments;
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    psEllipseAxes axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    fprintf (stderr, "axes: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    // get the initial model parameter guess
+    pmModel *model = pmSourceModelGuess (source, modelType);
+    // 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, recipe, 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 (fitMode == PM_SOURCE_FIT_PSF) {
+        pmModel *modelPSF = pmModelFromPSF (model, psf);
+        psFree (model);
+        model = modelPSF;
+        params = model->params->data.F32;
+    }
+
+    // list model input shape
+    psEllipseShape shape;
+    shape.sx  = 1.4 / model->params->data.F32[4];
+    shape.sy  = 1.4 / model->params->data.F32[5];
+    shape.sxy = model->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    fprintf (stderr, "guess: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+
+    fprintf (stderr, "input parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // define the pixels used for the fit
+    psImageKeepCircle (source->mask, xObj, yObj, RADIUS, "OR", PM_MASK_MARK);
+
+    char *fitset = psMetadataLookupStr (&status, recipe, "TEST_FIT_SET");
+    if (status) {
+        status = psphotFitSet (source, model, fitset, fitMode);
+        exit (0);
+    }
+
+    status = pmSourceFitModel (source, model, fitMode);
+
+    // measure the source mags
+    pmSourcePhotometryModel (&fitMag, model);
+    pmSourcePhotometryAper  (&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
+    pmModelSub (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: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotOutput.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotOutput.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotOutput.c	(revision 21632)
@@ -0,0 +1,219 @@
+# include "psphot.h"
+
+pmReadout *psphotSelectBackground (pmConfig *config, pmFPAview *view) {
+
+    bool status;
+    pmReadout *background;    
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKMDL");
+    if (!file) return NULL;
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+	background = file->readout;
+    } else {
+	background = pmFPAviewThisReadout (view, file->fpa);
+    }
+    return background;
+}
+
+bool psphotDumpConfig (pmConfig *config) {
+
+  psMetadataConfigWrite (config->site, "site.md");
+  psMetadataConfigWrite (config->camera, "camera.md");
+  psMetadataConfigWrite (config->recipes, "recipes.md");
+  psMetadataConfigWrite (config->arguments, "arguments.md");
+  psMetadataConfigWrite (config->files, "files.md");
+  return true;
+}
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteImage (fits, NULL, image, 0, NULL);
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+bool psphotDumpMoments (psMetadata *recipe, psArray *sources) {
+
+    bool status;
+
+    // optional dump of all rough source data
+    char *output = psMetadataLookupStr (&status, recipe, "MOMENTS_OUTPUT_FILE");
+    if (!status) return false;
+    if (output == NULL) return false;
+    if (output[0] == 0) return false;
+
+    pmMomentsWriteText (sources, output);
+    return true;
+}
+
+bool psphotDumpSource (pmSource *source, char *name) {
+
+    FILE *f = fopen (name, "w");
+    if (f == NULL) psAbort("can't open file");
+
+    for (int i = 0; i < source->pixels->numRows; i++) {
+        for (int j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->mask->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+
+	    fprintf (f, "%d %d %f %f %d\n", 
+		     (j + source->pixels->col0),
+		     (i + source->pixels->row0),
+		     source->pixels->data.F32[i][j],
+		     1.0 / source->weight->data.F32[i][j],
+		     source->mask->data.U8[i][j]);
+        }
+    }
+    fclose (f);
+    return true;
+}
+
+bool psphotAddPhotcode (psMetadata *recipe, pmConfig *config, pmFPAview *view) {
+
+    bool status;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    assert (status);
+
+    // determine PHOTCODE from fpa & view, overwrite in recipe
+    // XXX test this:
+    char *photcode = pmConceptsPhotcodeForView (config, input, view);
+    assert (photcode);
+
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "photcode from FPA concepts", photcode);
+    psLogMsg ("psphot", 3, "PHOTCODE is %s", photcode);
+
+    psFree (photcode);
+    return true;
+}
+
+bool psphotSetHeaderNstars (psMetadata *recipe, psArray *sources) {
+
+    int nSrc = 0;
+
+    // count the number of sources which will be written
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceSelectModel (source);
+        if (model == NULL)
+            continue;
+        nSrc ++;
+    }
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NSTARS", PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS", nSrc);
+    return true;
+}
+
+// these values are saved in an output header stub - they are added to either the
+// PHU header (CMP) or the MEF table header (CMF)
+psMetadata *psphotDefineHeader (psMetadata *recipe) {
+
+    psMetadata *header = psMetadataAlloc ();
+
+    // write necessary information to output header
+    psMetadataItemSupplement (header, recipe, "ZERO_PT");
+    psMetadataItemSupplement (header, recipe, "PHOTCODE");
+
+    psMetadataItemSupplement (header, recipe, "APMIFIT");
+    psMetadataItemSupplement (header, recipe, "DAPMIFIT");
+    psMetadataItemSupplement (header, recipe, "NAPMIFIT");
+    psMetadataItemSupplement (header, recipe, "SKYBIAS");
+    psMetadataItemSupplement (header, recipe, "SKYSAT");
+    
+    // PSF model parameters (shape values for image center)
+    psMetadataItemSupplement (header, recipe, "NPSFSTAR");
+    psMetadataItemSupplement (header, recipe, "APLOSS");
+    psMetadataItemSupplement (header, recipe, "FWHM_X");
+    psMetadataItemSupplement (header, recipe, "FWHM_Y");
+    psMetadataItemSupplement (header, recipe, "ANGLE");
+
+    // XXX these need to be defined from elsewhere
+    psMetadataAdd (header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+    psMetadataItemSupplement (header, recipe, "NSTARS");
+
+    // sky background model statistics
+    psMetadataItemSupplement (header, recipe, "SKY_MEAN");
+    psMetadataItemSupplement (header, recipe, "SKY_SIG");
+    psMetadataItemSupplement (header, recipe, "SKY_MIN");
+    psMetadataItemSupplement (header, recipe, "SKY_MAX");
+    psMetadataItemSupplement (header, recipe, "SKY_NX");
+    psMetadataItemSupplement (header, recipe, "SKY_NY");
+    
+    // XXX : don't require any of these about values to exist
+    psErrorClear ();
+
+    return header;
+}
+
+# if (0)
+// 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);
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    // sample PSF images??
+    if (psfSample != NULL) psphotSamplePSFs (psf, readout->image, psfSample);
+    if (psfSample != NULL) psphotSamplePSFs (psf, readout->image, psfSample);
+
+    if (psfFile != NULL) {
+	psMetadata *psfData = pmPSFtoMD (NULL, psf);
+	psMetadataConfigWrite (psfData, psfFile);
+	psFree (psfData);
+    }
+}
+
+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);
+    pmModelAdd (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);
+}
+# endif
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotParseCamera.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotParseCamera.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotParseCamera.c	(revision 21632)
@@ -0,0 +1,134 @@
+# include "psphot.h"
+
+// define the needed / desired I/O files
+bool psphotParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the input image defines the camera
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT", "INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_CONFIG, false, "Failed to build FPA from PSPHOT.INPUT");
+	return status;
+    }
+
+    // if we have requested PSPHOT.SRC, attempt to resolve it 
+    if (psMetadataLookupPtr(NULL, config->arguments, "SRC")) { 
+	pmFPAfileDefineFromArgs (&status, config, "PSPHOT.SRC", "SRC");
+	if (!status) {
+	    psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.SRC");
+	    return status;
+	}
+    }
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    // if MASK or WEIGHT was supplied on command line, bind files to input fpa
+    // XXX these do not quite yet work: pmFPAAddSourceFromHeader is not appropriate
+    pmFPAfileBindFromArgs (NULL, input, config, "PSPHOT.MASK", "MASK");
+    pmFPAfileBindFromArgs (NULL, input, config, "PSPHOT.WEIGHT", "WEIGHT");
+
+    // optionally load the PSF Model and/or fixed stars
+    pmFPAfileBindFromArgs (NULL, input, config, "PSPHOT.PSF", "PSF");
+
+    // set default recipe values here
+    // XXX drop this?  we put this in the default recipe?
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "FITMODE",         PS_META_NO_REPLACE, "default fitting mode", "NONE");
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "BREAK_POINT",     PS_META_NO_REPLACE, "default break point",  "NONE");
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ZERO_PT",         PS_META_NO_REPLACE, "default zero point",    25.00);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "BACKGROUND.XBIN", PS_META_NO_REPLACE, "default binning",          64);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "BACKGROUND.YBIN", PS_META_NO_REPLACE, "default binning",          64);
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE",        PS_META_NO_REPLACE, "default photcode",     "NONE");
+    // photcode will be added in psphotReadout
+
+    // XXX we need to be able to distinguish several cases:
+    // 1) the particular output data was not requested
+    // 2) the particular output data was requested but was not generated (skipped)
+    // 3) the particular output data was requested but was not generated (for valid failure)
+    // 4) the particular output data was requested but was not generated (surprising failure)
+
+    // these calls bind the I/O handle to the specified fpa
+    bool saveOutput = psMetadataLookupBool (NULL, recipe, "SAVE.OUTPUT");
+    if (saveOutput) {
+	if (!pmFPAfileDefineOutput (config, input->fpa, "PSPHOT.OUTPUT")) {
+	    psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.OUTPUT");
+	    return false;
+	}
+    }
+
+    // optionally save the residual image 
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.RESID")) {
+	if (!pmFPAfileDefineOutput (config, input->fpa, "PSPHOT.RESID")) {
+	    psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL");
+	    return false;
+	}
+    }
+
+    int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    // these calls construct a new fpa for the I/O handle 
+
+    // optionally save the background model (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL")) {
+	if (!pmFPAfileDefineFromFPA (config, input->fpa, DX, DY, "PSPHOT.BACKMDL")) {
+	    psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL");
+	    return false;
+	}
+    }
+    // optionally save the full background image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKGND")) {
+	if (!pmFPAfileDefineFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKGND")) {
+	    psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKGND");
+	    return false;
+	}
+    }
+    // optionally save the background-subtracted image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKSUB")) {
+	if (!pmFPAfileDefineFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKSUB")) {
+	    psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.");
+	    return false;
+	}
+    }
+    // optionally save the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PSF")) {
+	if (!pmFPAfileDefineOutput (config, input->fpa, "PSPHOT.PSF.SAVE")) {
+	    psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.PSF.SAVE");
+	    return false;
+	}
+    }
+
+    // optionally save output plots
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PLOTS")) {
+	if (!pmFPAfileDefineOutput (config, input->fpa, "SOURCE.PLOT.MOMENTS")) {
+	    psTrace ("psphot", 3, "Cannot find a rule for SOURCE.PLOT.MOMENTS");
+	}
+	if (!pmFPAfileDefineOutput (config, input->fpa, "SOURCE.PLOT.PSFMODEL")) {
+	    psTrace ("psphot", 3, "Cannot find a rule for SOURCE.PLOT.PSFMODEL");
+	}
+    }
+
+    // XXX add in example PSF image thumbnails
+    // pmFPAfileConstruct (config->files, format, config->camera, "PSPHOT.PSF_SAMPLE");
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, recipe, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	// select on the basis of extname?
+	pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+		psError(PSPHOT_ERR_CONFIG, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+    psTrace("psphot", 1, "Done with psphotParseCamera...\n");
+
+    psErrorClear();			// some metadata lookup may have failed
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotRadiusChecks.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotRadiusChecks.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotRadiusChecks.c	(revision 21632)
@@ -0,0 +1,98 @@
+# include "psphot.h"
+# define RADIUS_TYPE int
+
+static float PSF_FIT_NSIGMA;
+static float PSF_FIT_PADDING;
+static float PSF_FIT_RADIUS = 0;	// radius to use in fitting (ignored if <= 0,
+					// and a per-object radius is calculated)
+static pmModelRadius modelRadiusPSF;
+
+bool psphotInitRadiusPSF(const psMetadata *recipe,
+			 const pmModelType type)
+{
+    bool status = true;
+
+    PSF_FIT_NSIGMA  = psMetadataLookupF32(&status, recipe, "PSF_FIT_NSIGMA");
+    PSF_FIT_PADDING = psMetadataLookupF32(&status, recipe, "PSF_FIT_PADDING");
+    PSF_FIT_RADIUS =  psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS");
+
+    // this function specifies the radius at this the model hits the given flux
+    modelRadiusPSF       = pmModelRadius_GetFunction (type);
+    return true;
+}
+
+// call this function whenever you (re)-define the PSF model
+bool psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model)
+{
+    pmMoments *moments = source->moments;
+    // do we have a better value for the sky noise level?
+    // not really...
+
+    // set the fit radius based on the object flux limit and the model
+    float radiusFit = PSF_FIT_RADIUS;
+    if (radiusFit <= 0) {		// use fixed radius
+	if (moments == NULL) {
+	    radiusFit = modelRadiusPSF(model->params, PSF_FIT_NSIGMA*moments->dSky);
+	} else {
+	    radiusFit = modelRadiusPSF(model->params, 1.0);
+	}
+    }
+    model->radiusFit = (RADIUS_TYPE)(radiusFit + PSF_FIT_PADDING);
+
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+	
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	model->radiusFit *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radiusFit);
+    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->radiusFit = (RADIUS_TYPE) (modelRadiusPSF (model->params, PSF_FIT_NSIGMA*moments->dSky) + dR + PSF_FIT_PADDING);
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+	
+    if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+	model->radiusFit *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radiusFit);
+    return status;
+}
+
+static float EXT_FIT_NSIGMA;
+static float EXT_FIT_PADDING;
+static pmModelRadius modelRadiusEXT;
+
+bool psphotInitRadiusEXT (psMetadata *recipe, pmModelType type) {
+
+    bool status;
+
+    EXT_FIT_NSIGMA   = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA");
+    EXT_FIT_PADDING  = psMetadataLookupF32 (&status, recipe, "EXT_FIT_PADDING");
+
+    // this function specifies the radius at this the model hits the given flux
+    modelRadiusEXT       = pmModelRadius_GetFunction (type);
+    return true;
+}
+
+// call this function whenever you (re)-define the EXT model
+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->radiusFit = (RADIUS_TYPE) (modelRadiusEXT (model->params, EXT_FIT_NSIGMA*moments->dSky) + EXT_FIT_PADDING);
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+
+    // redefine the pixels if needed
+    bool status = pmSourceRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radiusFit);
+    return status;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReadout.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReadout.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReadout.c	(revision 21632)
@@ -0,0 +1,176 @@
+# include "psphot.h"
+
+bool psphotReadout (pmConfig *config, pmFPAview *view) {
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, false);
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    PS_ASSERT_PTR_NON_NULL (breakPt, false);
+
+    // generate mask & weight images if they don't already exit
+    if (!pmReadoutGenerateMaskWeight (readout, true)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble creating mask and/or weight");
+        return false;
+    }
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (recipe, config, view)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // I have a valid mask, now mask in the analysis region of interest
+    psphotMaskReadout (readout, recipe);
+
+    // run a single-model test if desired
+    psphotModelTest (readout, recipe);
+
+    if (psTraceGetLevel("psphot") > 5) {
+        psphotSaveImage (NULL, readout->image,  "image.fits");
+        psphotSaveImage (NULL, readout->mask,   "mask.fits");
+        psphotSaveImage (NULL, readout->weight, "weight.fits");
+    }
+
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup(config, readout, recipe, NULL, NULL);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotImageMedian (config, view)) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL);
+    }
+
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL);
+    }
+
+    // find the peaks in the image
+    psArray *peaks = psphotFindPeaks (readout, recipe, 1);
+    if (!peaks) {
+	psLogMsg ("psphot", 3, "unable to find peaks in this image");
+	return psphotReadoutCleanup (config, readout, recipe, NULL, NULL);
+    }
+
+    // construct sources and measure basic stats
+    psArray *sources = psphotSourceStats (readout, recipe, peaks);
+    if (!sources) return false;
+    psFree (peaks);
+
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        return psphotReadoutCleanup(config, readout, recipe, NULL, sources);
+    }
+
+    // mark blended peaks PS_SOURCE_BLEND
+    if (!psphotBasicDeblend (sources, recipe)) {
+	psLogMsg ("psphot", 3, "failed on deblend analysis");
+	return psphotReadoutCleanup (config, readout, recipe, NULL, sources);
+    }
+
+    // classify sources based on moments, brightness
+    if (!psphotRoughClass (sources, recipe)) {
+	psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+	return psphotReadoutCleanup (config, readout, recipe, NULL, sources);
+    }
+    if (!strcasecmp (breakPt, "MOMENTS")) {
+        return psphotReadoutCleanup(config, readout, recipe, NULL, sources);
+    }
+
+    // use bright stellar objects to measure PSF
+    pmPSF *psf = psphotChoosePSF (readout, sources, recipe);
+    if (psf == NULL) {
+	psLogMsg ("psphot", 3, "failure to construct a psf model");
+	return psphotReadoutCleanup (config, readout, recipe, psf, sources);
+    }
+    if (!strcasecmp (breakPt, "PSFMODEL")) {
+        return psphotReadoutCleanup (config, readout, recipe, psf, sources);
+    }
+
+    // include externally-supplied sources
+    psphotLoadExtSources (config, view, sources);
+
+    // construct an initial model for each object 
+    psphotGuessModels (readout, sources, recipe, psf);
+
+    // linear PSF fit to peaks
+    psphotEnsemblePSF (readout, sources, recipe, psf, FALSE);
+    if (!strcasecmp (breakPt, "ENSEMBLE")) {
+        goto finish;
+    }
+
+    // non-linear PSF and EXT fit to brighter sources
+    psphotBlendFit (readout, sources, recipe, psf);
+
+    // replace all sources
+    psphotReplaceAll (sources);
+
+    // linear PSF fit to remaining peaks
+    psphotEnsemblePSF (readout, sources, recipe, psf, TRUE);
+    if (!strcasecmp (breakPt, "PASS1")) {
+        goto finish;
+    }
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // re-measure background model (median, smoothed image)
+    psphotImageMedian (config, view);
+
+    // find the peaks in the image
+    psArray *newPeaks = psphotFindPeaks (readout, recipe, 2);
+
+    // define new sources based on the new peaks
+    psArray *newSources = psphotSourceStats (readout, recipe, newPeaks);
+    psFree (newPeaks);
+
+    // set source type
+    psphotRoughClass (newSources, recipe);
+
+    // create full input models
+    psphotGuessModels (readout, newSources, recipe, psf);
+
+    // replace all sources
+    psphotReplaceAll (sources);
+
+    // merge the newly selected peaks into the existing list
+    psphotMergeSources (sources, newSources);
+    psFree (newSources);
+
+    // linear PSF fit to remaining peaks
+    psphotEnsemblePSF (readout, sources, recipe, psf, TRUE);
+
+finish:
+
+    // measure aperture photometry corrections
+    if (!psphotApResid (readout, sources, recipe, psf)) {
+        psTrace ("psphot", 4, "failure on psphotApResid");
+        psError(PSPHOT_ERR_PHOTOM, false, "Measure aperture photometry corrections");
+        return false;
+    }
+
+    // calculate source magnitudes
+    // XXX modify this API to take config, view?
+    pmReadout *background = psphotSelectBackground (config, view);
+    psphotMagnitudes(sources, recipe, psf, background);
+
+    // replace failed sources?
+    // psphotReplaceUnfit (sources);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (sources);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, readout, recipe, psf, sources);
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReadoutCleanup.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReadoutCleanup.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReadoutCleanup.c	(revision 21632)
@@ -0,0 +1,59 @@
+# include "psphot.h"
+
+// psphotReadoutCleanup is called on exit from psphotReadout if the last raised
+// error is not a DATA error, then there was a serious problem.  only in this
+// case, or if the fail on the stats measurement, do we return false
+bool psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmPSF *psf, psArray *sources) {
+
+    if (psErrorCodeLast() == PSPHOT_ERR_DATA) {
+        psErrorStackPrint(stderr, "Error in the psphot readout analysis");
+	psErrorClear();
+    } 
+    if (psErrorCodeLast() != PS_ERR_NONE) return false;
+
+    // use the psf-model to measure FWHM stats
+    if (psf) {
+	// don't call psphotPSFstats unless we have a valid pdf
+	// if it fails, there is probably a programming error 
+        if (!psphotPSFstats (readout, recipe, psf)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure PSF shape parameters");
+            return false;
+        }
+    }
+    if (!psf && sources) {
+        if (!psphotMomentsStats (readout, recipe, sources)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure Moment shape parameters");
+            return false;
+        }
+    }
+
+    // write NSTARS to the image header
+    psphotSetHeaderNstars (recipe, sources);
+
+    // create an output header with stats results
+    psMetadata *header = psphotDefineHeader (recipe);
+
+    // save the results of the analysis
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", header);
+    if (sources) {
+	psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY,    "psphot sources", sources);
+    }
+    if (psf) {
+	psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF",     PS_DATA_UNKNOWN,  "psphot psf", psf);
+    }
+
+    // remove internal pmFPAfiles, if created
+    pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+    pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+
+    if (psErrorCodeLast() != PS_ERR_NONE) abort();
+
+    // XXX move this to top of loop
+    pmKapaClose ();
+
+    psFree (psf);
+    psFree (header);
+    psFree (sources);
+
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReplaceUnfit.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReplaceUnfit.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotReplaceUnfit.c	(revision 21632)
@@ -0,0 +1,54 @@
+# include "psphot.h"
+
+bool psphotReplaceUnfit (psArray *sources) { 
+
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (source->mode & PM_SOURCE_MODE_FAIL) goto replace;
+      continue;
+
+    replace:
+	if (source->modelPSF == NULL) continue;
+
+	psTrace ("psphot", 3, "replacing object at %f,%f\n", 
+		 source->modelPSF->params->data.F32[2], source->modelPSF->params->data.F32[3]);
+
+	pmModelAdd (source->pixels, source->mask, source->modelPSF, false, false);
+	source->mode &= ~PM_SOURCE_MODE_SUBTRACTED;
+	source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    }
+    psLogMsg ("psphot.replace", 3, "replace unfitted models: %f sec (%ld objects)\n", psTimerMark ("psphot"), sources->n);
+    return true;
+}
+
+bool psphotReplaceAll (psArray *sources) { 
+
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (!(source->mode & PM_SOURCE_MODE_SUBTRACTED)) continue;
+
+      // select appropriate model
+      pmModel *model = pmSourceGetModel (NULL, source);
+      if (model == NULL) continue;  // model must be defined
+	
+      psTrace ("psphot", 3, "replacing object at %f,%f\n", 
+	       model->params->data.F32[2], model->params->data.F32[3]);
+
+      pmModelAdd (source->pixels, source->mask, model, false, false);
+      source->mode &= ~PM_SOURCE_MODE_SUBTRACTED;
+    }
+    psLogMsg ("psphot.replace", PS_LOG_INFO, "replace models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotRoughClass.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotRoughClass.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotRoughClass.c	(revision 21632)
@@ -0,0 +1,35 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+bool psphotRoughClass (psArray *sources, psMetadata *recipe) {
+
+    pmPSFClump   psfClump;
+
+    psTimerStart ("psphot");
+
+    psfClump = pmSourcePSFClump (sources, recipe);
+    if (psfClump.X < 0) {
+	psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourcePSFClump");
+	return false;
+    }
+    if (!psfClump.X || !psfClump.Y) {
+	psError(PSPHOT_ERR_DATA, true, "Failed to find a valid PSF clump");
+	return false;
+    }
+    psLogMsg ("psphot", 3, "psf clump  X,  Y: %f, %f\n", psfClump.X, psfClump.Y);
+    psLogMsg ("psphot", 3, "psf clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
+
+    // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
+    if (!pmSourceRoughClass (sources, recipe, psfClump)) {
+	psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourceRoughClass");
+	return false;
+    }
+
+    // optional printout of source moments only
+    psphotDumpMoments (recipe, sources);
+
+    psLogMsg ("psphot.roughclass", PS_LOG_INFO, "rough classification: %f sec\n", psTimerMark ("psphot"));
+
+    return true;
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSkyReplace.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSkyReplace.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSkyReplace.c	(revision 21632)
@@ -0,0 +1,33 @@
+# include "psphot.h"
+
+// XXX make this an option?
+// in order to  successfully replace the sky, we must define a corresponding file...
+bool psphotSkyReplace (pmConfig *config, pmFPAview *view) {
+
+    psTimerStart ("psphot");
+
+    // find the currently selected readout
+    pmReadout *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    if (readout == NULL) psAbort("input not defined");
+
+    // select background pixels, from output background file, or create
+    pmReadout *background = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKGND");
+    if (background == NULL) psAbort("background not defined");
+
+    // select the corresponding images
+    psF32 **image = readout->image->data.F32;
+    psU8  **mask  = readout->mask->data.U8;
+    psF32 **back  = background->image->data.F32;
+
+    // replace the background model
+    for (int j = 0; j < readout->image->numRows; j++) {
+	for (int i = 0; i < readout->image->numCols; i++) {
+	    if (!mask[j][i]) {
+		image[j][i] += back[j][i];
+	    }
+	}
+    }
+    psLogMsg ("psphot.sky", PS_LOG_DETAIL, "replace background flux : %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSortBySN.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSortBySN.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSortBySN.c	(revision 21632)
@@ -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->peak == NULL) ? 0 : A->peak->SN;
+    psF32 fB = (B->peak == NULL) ? 0 : B->peak->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->peak == NULL) ? 0 : A->peak->y;
+    psF32 fB = (B->peak == NULL) ? 0 : B->peak->y;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (+1);
+    if (diff < FLT_EPSILON) return (-1);
+    return (0);
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceFits.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceFits.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceFits.c	(revision 21632)
@@ -0,0 +1,385 @@
+# include "psphot.h"
+
+// given a source with an existing modelPSF, attempt a full PSF fit, subtract if successful
+// XXX this function does not call pmSourceFitModelInit : fix this?
+
+static int NfitPSF = 0;
+static int NfitBlend = 0;
+static int NfitDBL = 0;
+static int NfitEXT = 0;
+
+bool psphotFitInit () {
+    psTimerStart ("psphot.fits");
+    return true;
+}
+
+bool psphotFitSummary () {
+
+    fprintf (stderr, "fitted %5d psf, %5d blend, %5d ext, %5d dbl : %6.2f sec\n", 
+	     NfitPSF, NfitBlend, NfitEXT, NfitDBL, psTimerMark ("psphot.fits"));
+    return true;
+}
+
+bool psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf) {
+
+    float x, y, dR;
+
+    // if this source is not a possible blend, just fit as PSF
+    if ((source->blends == NULL) || (source->mode & PM_SOURCE_MODE_SATSTAR)) {
+        bool status = psphotFitPSF (readout, source, psf);
+        return status;
+    }
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+
+    if (isnan(PSF->params->data.F32[PM_PAR_I0])) psAbort("nan in blend fit primary");
+
+    x = PSF->params->data.F32[PM_PAR_XPOS];
+    y = PSF->params->data.F32[PM_PAR_YPOS];
+
+    psArray *modelSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (modelSet, 16, PSF);
+
+    psArray *sourceSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (sourceSet, 16, source);
+
+    // we need to include all blends in the fit (unless primary is saturated?)
+    dR = 0;
+    for (int i = 0; i < source->blends->n; i++) {
+        pmSource *blend = source->blends->data[i];
+
+        // find the blend which is furthest from source
+        dR = PS_MAX (dR, hypot (blend->peak->xf - x, blend->peak->yf - y));
+
+        // create the model and guess parameters for this blend
+        pmModel *model = pmModelAlloc (PSF->type);
+        for (int j = 0; j < model->params->n; j++) {
+            model->params->data.F32[j] = PSF->params->data.F32[j];
+            model->dparams->data.F32[j] = PSF->dparams->data.F32[j];
+        }
+
+        // XXX assume local sky is 0.0?
+        model->params->data.F32[PM_PAR_I0] = blend->peak->flux;
+        model->params->data.F32[PM_PAR_XPOS] = blend->peak->xf;
+        model->params->data.F32[PM_PAR_YPOS] = blend->peak->yf;
+
+	// these should never be invalid values
+	// XXX drop these tests eventually
+        if (isnan(model->params->data.F32[PM_PAR_I0])) psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_XPOS])) psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_YPOS])) psAbort("nan in blend fit");
+
+        // add this blend to the list
+        psArrayAdd (modelSet, 16, model);
+        psArrayAdd (sourceSet, 16, blend);
+
+        // free to avoid double counting model
+        psFree (model);
+    }
+
+    // extend source radius as needed
+    psphotCheckRadiusPSFBlend (readout, source, PSF, dR);
+
+    // fit PSF model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "OR", PM_MASK_MARK);
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    // correct model chisq for flux trend
+    double chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // evaluate the blend objects, subtract if good, free otherwise
+    for (int i = 1; i < modelSet->n; i++) {
+        pmSource *blend = sourceSet->data[i];
+        pmModel *model  = modelSet->data[i];
+
+        // correct model chisq for flux trend
+        chiTrend = psPolynomial1DEval (psf->ChiTrend, model->params->data.F32[PM_PAR_I0]);
+        model->chisqNorm = model->chisq / chiTrend;
+
+        // if this one failed, skip it
+        if (!psphotEvalPSF (blend, model)) {
+            psTrace ("psphot", 4, "failed on blend %d of %ld\n", i, modelSet->n);
+            continue;
+        }
+
+        // otherwise, supply the resulting model to the corresponding blend
+        psFree(blend->modelPSF);
+        blend->modelPSF = psMemIncrRefCounter (model);
+        psTrace ("psphot", 5, "fitted blend as PSF\n");
+        pmModelSub (source->pixels, source->mask, model, false, false);
+        blend->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+        blend->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    }
+    NfitBlend += modelSet->n;
+
+    // evaluate the primary object
+    if (!psphotEvalPSF (source, PSF)) {
+        psTrace ("psphot", 4, "failed on blend 0 of %ld\n", modelSet->n);
+        psFree (PSF);
+	psFree (modelSet);
+	psFree (sourceSet);
+        return false;
+    }
+    psFree (modelSet);
+    psFree (sourceSet);
+
+    psTrace ("psphot", 5, "fitted primary as PSF\n");
+    pmModelSub (source->pixels, source->mask, PSF, false, false);
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    return true;
+}
+
+bool psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf) {
+
+    float x, y;
+    double chiTrend;
+
+    NfitPSF ++;
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in psf fit");
+
+    // extend source radius as needed
+    psphotCheckRadiusPSF (readout, source, PSF);
+
+    x = PSF->params->data.F32[PM_PAR_XPOS];
+    y = PSF->params->data.F32[PM_PAR_YPOS];
+
+    // fit PSF model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, PSF, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    // correct model chisq for flux trend
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // does the PSF model succeed?
+    if (!psphotEvalPSF (source, PSF)) {
+        psFree (PSF);
+        return false;
+    }
+
+    psTrace ("psphot", 5, "fitted as PSF\n");
+    pmModelSub (source->pixels, source->mask, PSF, false, false);
+
+    // free old model, save new model
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+
+    return true;
+}
+
+static float EXT_MIN_SN;
+static float EXT_MOMENTS_RAD;
+static pmModelType modelTypeEXT;
+
+bool psphotInitLimitsEXT (psMetadata *recipe) {
+
+    bool status;
+
+    // extended source model parameters
+    EXT_MIN_SN       = psMetadataLookupF32 (&status, recipe, "EXT_MIN_SN");
+    EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, recipe, "EXT_MOMENTS_RADIUS");
+
+    // extended source model descriptions
+    char *modelNameEXT = psMetadataLookupStr (&status, recipe, "EXT_MODEL");
+    modelTypeEXT = pmModelSetType (modelNameEXT);
+    psphotInitRadiusEXT (recipe, modelTypeEXT);
+
+    return true;
+}
+
+bool psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf) {
+
+    bool okEXT, okDBL;
+    float chiEXT, chiDBL;
+    double chiTrend;
+    pmModel *ONE = NULL;
+
+    // skip the source if we don't think it is extended
+    if (source->type == PM_SOURCE_TYPE_UNKNOWN) return false;
+    if (source->type == PM_SOURCE_TYPE_DEFECT) return false;
+    if (source->type == PM_SOURCE_TYPE_SATURATED) return false;
+    if (source->moments->SN < EXT_MIN_SN) return false;
+
+    // recalculate the source moments using the larger extended-source moments radius
+    if (!pmSourceMoments (source, EXT_MOMENTS_RAD)) return false;
+
+    psTrace ("psphot", 5, "trying blob...\n");
+
+    // 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]);
+    // XXX should I keep / save the flags set in the eval functions?
+
+    // correct first model chisqs for flux trend
+    ONE = DBL->data[0];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    // save chisq for double-star/galaxy comparison
+    chiDBL = ONE->chisq / ONE->nDOF;
+
+    // correct second model chisqs for flux trend
+    ONE = DBL->data[1];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    psFree (tmpSrc);
+
+    if (okEXT && okDBL) {
+        psTrace ("psphot", 5, "blob chisq: %f vs %f\n", chiEXT, chiDBL);
+        // XXX EAM : a bogus bias: need to examine this better
+        if (3*chiEXT > chiDBL) goto keepDBL;
+        goto keepEXT;
+    }
+
+    if (okEXT && !okDBL) goto keepEXT;
+    if (!okEXT && okDBL) goto keepDBL;
+
+    // both models failed; reject them both
+    psFree (EXT);
+    psFree (DBL);
+    return false;
+
+keepEXT:
+    // sub EXT
+    psFree (DBL);
+    pmModelSub (source->pixels, source->mask, EXT, false, false);
+    psTrace ("psphot", 5, "blob as EXT: %f %f\n", EXT->params->data.F32[PM_PAR_XPOS], EXT->params->data.F32[PM_PAR_YPOS]);
+
+    // save new model
+    source->modelEXT = EXT;
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    return true;
+
+keepDBL:
+    // sub DLB
+    psFree (EXT);
+    pmModelSub (source->pixels, source->mask, (pmModel *) DBL->data[0], false, false);
+    pmModelSub (source->pixels, source->mask, (pmModel *) DBL->data[1], false, false);
+    psTrace ("psphot", 5, "blob as DBL: %f %f\n", ONE->params->data.F32[PM_PAR_XPOS], ONE->params->data.F32[PM_PAR_YPOS]);
+
+    // drop old model, save new second model...
+    psFree (source->modelPSF);
+    source->modelPSF = psMemIncrRefCounter (DBL->data[0]);
+    source->mode    |= PM_SOURCE_MODE_SUBTRACTED;
+    source->mode    |= PM_SOURCE_MODE_PAIR;
+    source->mode    &= ~PM_SOURCE_MODE_TEMPSUB;
+
+    // copy most data from the primary source (modelEXT, blends stay NULL)
+    pmSource *newSrc = pmSourceAlloc ();
+    newSrc->peak     = psMemIncrRefCounter (source->peak);
+    newSrc->pixels   = psMemIncrRefCounter (source->pixels);
+    newSrc->weight   = psMemIncrRefCounter (source->weight);
+    newSrc->mask     = psMemIncrRefCounter (source->mask);
+    newSrc->moments  = psMemIncrRefCounter (source->moments);
+    newSrc->modelPSF = psMemIncrRefCounter (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;
+    psEllipseAxes axes;
+    psEllipseMoments moments;
+    psArray *modelSet;
+
+    NfitDBL ++;
+
+    // make a guess at the position of the two sources
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    // XXX this is really arbitrary: 4 pixel separation?
+    dx = 2 * cos (axes.theta);
+    dy = 2 * sin (axes.theta);
+
+    // save the PSF model from the Ensemble fit
+    PSF = source->modelPSF;
+    psphotCheckRadiusPSFBlend (readout, source, PSF, 8.0);
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in dbl fit");
+
+    modelSet = psArrayAlloc (2);
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf + dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf + dy;
+    modelSet->data[0] = DBL;
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf - dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf - dy;
+    modelSet->data[1] = DBL;
+
+    x = source->moments->x;
+    y = source->moments->y;
+
+    // fit PSF model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "OR", PM_MASK_MARK);
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    return (modelSet);
+}
+
+pmModel *psphotFitEXT (pmReadout *readout, pmSource *source) {
+
+    float x, y;
+
+    NfitEXT ++;
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *EXT = pmSourceModelGuess (source, modelTypeEXT);
+    assert (EXT != NULL);
+	
+    // if (isnan(EXT->params->data.F32[1])) psAbort("nan in ext fit");
+
+    psphotCheckRadiusEXT (readout, source, EXT);
+
+    x = EXT->params->data.F32[PM_PAR_XPOS];
+    y = EXT->params->data.F32[PM_PAR_YPOS];
+
+    if ((source->moments->Sx < 1e-3) || (source->moments->Sx < 1e-3)) {
+        psTrace ("psphot", 5, "problem source: moments: %f %f\n", source->moments->Sx, source->moments->Sy);
+    }
+
+    // fit EXT (not PSF) model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, EXT->radiusFit, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, EXT, PM_SOURCE_FIT_EXT);
+    psImageKeepCircle (source->mask, x, y, EXT->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    return (EXT);
+}
+
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceFreePixels.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceFreePixels.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceFreePixels.c	(revision 21632)
@@ -0,0 +1,12 @@
+# include "psphot.h"
+
+void psphotSourceFreePixels (psArray *sources) {
+
+    if (!sources) return;
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	pmSourceFreePixels (source);
+    }
+    return;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceStats.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceStats.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSourceStats.c	(revision 21632)
@@ -0,0 +1,99 @@
+# include "psphot.h"
+
+psArray *psphotSourceStats (pmReadout *readout, psMetadata *recipe, psArray *peaks)
+{
+    bool     status  = false;
+    psArray *sources = NULL;
+    float BIG_RADIUS;
+
+    psTimerStart ("psphot");
+
+    // determine properties (sky, moments) of initial sources
+    float INNER    = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    if (!status) return NULL;
+    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    if (!status) return NULL;
+    float RADIUS   = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    if (!status) return NULL;
+    float MIN_SN   = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN");
+    if (!status) return NULL;
+    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
+    if (!status) return NULL;
+
+    sources = psArrayAllocEmpty (peaks->n);
+
+    int Nfail = 0;
+    int Nmoments = 0;
+    for (int i = 0; i < peaks->n; i++) {
+
+        // create a new source, add peak
+        pmSource *source = pmSourceAlloc();
+        source->peak = (pmPeak *)psMemIncrRefCounter(peaks->data[i]);
+
+        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
+        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+        if (!strcasecmp (breakPt, "PEAKS")) {
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            continue;
+        }
+
+        // skip faint sources
+	if (source->peak->SN < MIN_SN) {
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+	    continue;
+	}
+
+        // measure a local sky value
+        // XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+        status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER);
+        if (!status) {
+          psFree (source);
+	  Nfail ++;
+          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);
+	  Nfail ++;
+          continue;
+        }
+
+        // measure basic source moments
+        status = pmSourceMoments (source, RADIUS);
+        if (status) {
+            // add to the source array
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+	    Nmoments ++;
+            continue;
+        }
+
+        // if no valid pixels, or massive swing, likely saturated source,
+        // try a much larger box
+        BIG_RADIUS = PS_MIN (INNER, 3*RADIUS);
+        psTrace ("psphot", 4, "retrying moments for %d, %d\n", source->peak->x, source->peak->y);
+        status = pmSourceMoments (source, BIG_RADIUS);
+        if (status) {
+            // add to the source array
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+	    Nmoments ++;
+            continue;
+        }
+
+        psFree (source);
+	Nfail ++;
+        continue;
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld sources, %d moments, %d failed: %f sec\n", sources->n, Nmoments, Nfail, psTimerMark ("psphot"));
+
+    return (sources);
+}
+
+// XXX EAM : filter out bad peaks (eg, no valid pixels available for sky)
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSummaryPlots.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSummaryPlots.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotSummaryPlots.c	(revision 21632)
@@ -0,0 +1,281 @@
+# include "psphot.h"
+
+// the top portion of this file defines plotting functions which use kapa for plotting.
+// if kapa is not available, these functions are defined in the bottom portion as stubs
+// which perform NOP and return false (XXX should this be true??)
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+
+# include <kapa.h>
+
+// plot the sx, sy moments plane (faint and bright sources)
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.MOMENT.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping moments plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating moments plot");
+
+    // XXX get the 'showWindow' option from the recipes somewhere
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+
+    KapaResize (kapa, 500, 500);
+    KapaInitGraph (&graphdata);
+
+    // examine sources to set data range
+    graphdata.xmin = -0.05;
+    graphdata.ymin = -0.05;
+    graphdata.xmax = +2.05;
+    graphdata.ymax = +2.05;
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "&ss&h_x| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "&ss&h_y| (pixels)", KAPA_LABEL_YM);
+	       
+    psVector *xBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *xFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // construct the vectors
+    int nB = 0;
+    int nF = 0;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+    
+	xFaint->data.F32[nF] = source->moments->Sx;
+	yFaint->data.F32[nF] = source->moments->Sy;
+	nF++;
+    
+	// XXX make this a user-defined cutoff
+	if (source->moments->SN < 25) continue;
+
+	xBright->data.F32[nB] = source->moments->Sx;
+	yBright->data.F32[nB] = source->moments->Sy;
+	nB++;
+    }
+    xFaint->n = nF;
+    yFaint->n = nF;
+
+    xBright->n = nB;
+    yBright->n = nB;
+  
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nF, &graphdata);
+    KapaPlotVector (kapa, nF, xFaint->data.F32);
+    KapaPlotVector (kapa, nF, yFaint->data.F32);
+  
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nB, &graphdata);
+    KapaPlotVector (kapa, nB, xBright->data.F32);
+    KapaPlotVector (kapa, nB, yBright->data.F32);
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xBright);
+    psFree (yBright);
+    psFree (xFaint);
+    psFree (yFaint);
+
+    return true;
+}
+
+// plot the sx, sy, sxy as vector field, 
+// plot the PSF measured sx, sy, sxy as vector field
+// pull the sources from the config / file?
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.PSFMODEL.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping psf model plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating psf model plot");
+
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    // XXX make the aspect-ratio match the image
+    KapaResize (kapa, 800, 800);
+    KapaInitGraph (&graphdata);
+
+    psVector *xMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+
+    // construct the plot vectors
+    int nMNT = 0;
+    int nPSF = 0;
+    int nMIN = 0;
+    float dx = 0;
+    float dy = 0;
+    float scale = 10;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+	if (source->moments->SN < 25) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+    
+	pmModel *model = source->modelPSF;
+        if (model == NULL) continue;
+
+	psF32 *PAR = model->params->data.F32;
+
+	psEllipseMoments moments;
+	moments.x2 = source->moments->Sx;
+	moments.y2 = source->moments->Sy;
+	moments.xy = source->moments->Sxy;
+
+	psEllipseShape shape;
+	shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+	shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+	shape.sxy = PAR[PM_PAR_SXY];
+
+	// force the axis ratio to be < 20.0
+	psEllipseAxes axes_mnt = psEllipseMomentsToAxes (moments, 20.0);
+	psEllipseAxes axes_psf = psEllipseShapeToAxes (shape, 20.0);
+
+	// moments major axis
+	dx = scale*axes_mnt.major*cos(axes_mnt.theta);
+	dy = scale*axes_mnt.major*sin(axes_mnt.theta);
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] - dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] - dy;
+	nMNT++;
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] + dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] + dy;
+	nMNT++;
+    
+	// psf major axis
+	dx = scale*axes_psf.major*cos(axes_psf.theta);
+	dy = scale*axes_psf.major*sin(axes_psf.theta);
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] - dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] - dy;
+	nPSF++;
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] + dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] + dy;
+	nPSF++;
+
+	// minor axis (to show size)
+	dy = +scale*axes_psf.minor*cos(axes_psf.theta);
+	dx = -scale*axes_psf.minor*sin(axes_psf.theta);
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] - dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] - dy;
+	nMIN++;
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] + dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] + dy;
+	nMIN++;
+
+	graphdata.xmin = PS_MIN(graphdata.xmin, PAR[PM_PAR_XPOS]);
+	graphdata.xmax = PS_MAX(graphdata.xmax, PAR[PM_PAR_XPOS]);
+	graphdata.ymin = PS_MIN(graphdata.ymin, PAR[PM_PAR_YPOS]);
+	graphdata.ymax = PS_MAX(graphdata.ymax, PAR[PM_PAR_YPOS]);
+    }
+    xMNT->n = yMNT->n = nMNT;
+    xPSF->n = yPSF->n = nPSF;
+    xMIN->n = yMIN->n = nMIN;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX set the plot range to match the image
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "x (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "y (pixels)", KAPA_LABEL_YM);
+    KapaSendLabel (kapa, "vector is major axis (scale by 20) : black are moments, blue are psf model, red is psf minor axis", KAPA_LABEL_XP);
+	       
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 100;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMNT, &graphdata);
+    KapaPlotVector (kapa, nMNT, xMNT->data.F32);
+    KapaPlotVector (kapa, nMNT, yMNT->data.F32);
+  
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nPSF, &graphdata);
+    KapaPlotVector (kapa, nPSF, xPSF->data.F32);
+    KapaPlotVector (kapa, nPSF, yPSF->data.F32);
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMIN, &graphdata);
+    KapaPlotVector (kapa, nMIN, xMIN->data.F32);
+    KapaPlotVector (kapa, nMIN, yMIN->data.F32);
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xMNT);
+    psFree (yMNT);
+    psFree (xPSF);
+    psFree (yPSF);
+    psFree (xMIN);
+    psFree (yMIN);
+
+    return true;
+}
+
+# else
+
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping moments plot");
+    return true;
+}
+
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping psf model plot");
+    return true;
+}
+
+# endif
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTest.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTest.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTest.c	(revision 21632)
@@ -0,0 +1,83 @@
+# 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 = psArrayAllocEmpty (10);
+
+    for (int i = 0; i < 10; i++) {
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "ROW",   PS_DATA_S32,    "", i);
+        psMetadataAdd (row, PS_LIST_TAIL, "FROW",  PS_TYPE_F32,    "", 0.1*i);
+        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY", PS_DATA_STRING, "", "test line");
+
+        table->data[i] = row;
+    }
+    table->n = 10;
+
+    psMetadata *theader = psMetadataAlloc ();
+    psMetadataAdd (theader, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING, "extension name", "SMPFILE");
+
+    psFits *fits = psFitsOpen (argv[3], "w");
+    // psFitsWriteImage (fits, header, tmpimage, 0);
+    psFitsWriteHeader (header, fits);
+    psFitsWriteTable (fits, theader, table);
+
+# endif
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTestArguments.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTestArguments.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTestArguments.c	(revision 21632)
@@ -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);
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTestPSF.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTestPSF.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotTestPSF.c	(revision 21632)
@@ -0,0 +1,131 @@
+# include "psphot.h"
+
+bool psphotTestPSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool            status;
+    char           *modelName;
+    pmPSF          *psf = NULL;
+    psArray        *stars = NULL;
+
+    psTimerStart ("psphot");
+
+    psphotSaveImage (NULL, readout->image,  "image.fits");
+
+    // check if a PSF model is supplied by the user
+    psf = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.PSF");
+    if (psf != NULL) return psf;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    if (!status) {
+        NSTARS = PS_MIN (sources->n, 200);
+        psWarning("PSF_MAX_NSTARS is not set in the recipe --- defaulting to %d\n", NSTARS);
+    }
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) {
+        POISSON_ERRORS = true;
+        psWarning("POISSON_ERRORS is not set in the recipe --- defaulting to true.\n");
+    }
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // how to model the PSF variations across the field
+    // XXX make a default value?  or not?
+    psMetadata *md = psMetadataLookupMetadata (&status, recipe, "PSF.TREND.MASK");
+    psPolynomial2D *psfTrendMask;
+    if (!status || !md) {
+        psWarning("PSF.TREND.MASK is not set in the recipe --- defaulting to use zeroth order.\n");
+        psfTrendMask = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 0, 0);
+    } else {
+        psfTrendMask = psPolynomial2DfromMetadata (md);
+        if (!psfTrendMask) {
+            psError(PSPHOT_ERR_PSF, true, "Unable to construct polynomial from PSF.TREND.MASK in the recipe");
+            return false;
+        }
+    }
+
+    stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; (i < sources->n) && (stars->n < NSTARS); i++) {
+        pmSource *source = sources->data[i];
+        // if (source->mode & PM_SOURCE_MODE_PSFSTAR) psArrayAdd (stars, 200, source);
+        psArrayAdd (stars, 200, source);
+    }
+    psLogMsg ("psphot.pspsf", 4, "selected candidate %ld PSF objects\n", stars->n);
+
+    if (stars->n == 0) {
+        psError(PSPHOT_ERR_PSF, true, "Failed to find any PSF candidates");
+        return NULL;
+    }
+
+    // get the fixed PSF fit radius
+    // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    if (!status) {
+        psWarning("PSF_FIT_RADIUS is not set in the recipe --- defaulting to 20.0\n");
+        RADIUS = 20.0;
+    }
+
+    // for this test, require a single model
+    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+    if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+    if (mdi->type != PS_DATA_STRING) psAbort("choose a single PSF_MODEL");
+    modelName = mdi->data.V;
+
+    pmPSFtestModel (stars, modelName, RADIUS, POISSON_ERRORS, psfTrendMask);
+
+    psphotSaveImage (NULL, readout->image,  "resid.fits");
+    psphotSaveImage (NULL, readout->mask,   "mask.fits");
+    psphotSaveImage (NULL, readout->weight, "weight.fits");
+    
+    return true;
+}
+
+bool pmPSFtestModel (psArray *sources, char *modelName, float RADIUS, bool poissonErrors, psPolynomial2D *psfTrendMask)
+{
+    bool status;
+    float x;
+    float y;
+
+    pmModelType type = pmModelSetType (modelName);
+    pmPSF *psf = pmPSFAlloc (type, poissonErrors, psfTrendMask);
+    if (psf == NULL) psAbort("unknown model");
+
+    FILE *f = fopen ("params.dat", "w");
+
+    // stage 1:  fit an independent model (freeModel) to all sources
+    psTimerStart ("fit");
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel  *model  = pmSourceModelGuess (source, psf->type);
+        x = source->peak->x;
+        y = source->peak->y;
+
+        // set temporary object mask and fit object
+        // fit model as EXT, not PSF
+        psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PM_MASK_MARK);
+        status = pmSourceFitModel (source, model, PM_SOURCE_FIT_EXT);
+        psImageKeepCircle (source->mask, x, y, RADIUS, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+	// write fitted parameters to file
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_XPOS]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_YPOS]);
+
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXX]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SYY]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXY]);
+
+	fprintf (f, "%f %d\n", model->chisq, model->nIter);
+
+	// subtract model flux
+	pmModelSub (source->pixels, source->mask, model, false, false);
+    }
+    fclose (f);
+    psLogMsg ("psphot.psftest", 4, "fit ext: %f sec for %ld sources\n", psTimerMark ("fit"), sources->n);
+    return true;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotVersion.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotVersion.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotVersion.c	(revision 21632)
@@ -0,0 +1,40 @@
+#include "psphot.h"
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+# endif
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString psphotVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString psphotVersionLong(void)
+{
+    psString version = psphotVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+
+# if (HAVE_KAPA)
+    psString ohanaVersion = psStringStripCVS (ohana_version(), "Name");
+    psString libdvoVersion = psStringStripCVS (libdvo_version(), "Name");
+
+    psStringAppend (&version, " with libkapa (ohana %s, libdvo: %s)", ohanaVersion, libdvoVersion);
+    psFree (ohanaVersion);
+    psFree (libdvoVersion);
+# else
+    psStringAppend (&version, " WITHOUT libkapa");
+# endif
+
+    psFree(tag);
+    return version;
+}
+
+// Defined by RHL; leaving for backwards compatibility.
+const char *psphotCVSName(void) {
+   return cvsTag;
+}
Index: /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotWeightBias.c
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotWeightBias.c	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/psphot/src/psphotWeightBias.c	(revision 21632)
@@ -0,0 +1,89 @@
+# include "psphot.h"
+
+// select objects fitted with PSF model
+// re-fit all of them with the non-poisson errors
+// only allow the normalization to vary
+// XXX eventually, we should be able to do this with linear fitting...
+bool psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    int Nfit = 0;
+    bool status;
+    psF32 *PARp, *PARc;
+    psF32 x, y;
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+
+    // set fitting method to use non-poisson errors (local sky error)
+    float SKY_SIG = psMetadataLookupF32 (&status, recipe, "SKY_SIG");
+    if (!status) {
+      SKY_SIG = 1.0;
+        psWarning("SKY_SIG is not set --- defaulting to %f\n", SKY_SIG);
+    }
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) {
+        POISSON_ERRORS = true;
+        psWarning("POISSON_ERRORS is not set in the recipe --- defaulting to true.\n");
+    }
+    pmSourceFitModelInit (15, 0.1, PS_SQR(SKY_SIG), POISSON_ERRORS);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    FILE *f = fopen ("bias.dat", "w");
+    if (f == NULL) psAbort("can't open output file");
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip lower-quality objects
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+        if (source->mode &  PM_SOURCE_MODE_POOR) continue;
+        if (source->mode &  PM_SOURCE_MODE_FAIL) continue;
+        if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+        if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        if (source->mode &  PM_SOURCE_MODE_SATSTAR) continue;
+
+        // if model is NULL, we don't have a starting guess
+        if (source->modelPSF == NULL) continue;
+
+        if (source->moments->x < AnalysisRegion.x0) continue;
+        if (source->moments->y < AnalysisRegion.y0) continue;
+        if (source->moments->x > AnalysisRegion.x1) continue;
+        if (source->moments->y > AnalysisRegion.y1) continue;
+
+        // replace object in image
+        pmModelAdd (source->pixels, source->mask, source->modelPSF, false, false);
+
+        // make a temporary model (we don't keep the result of this analysis)
+        pmModel *PSF = pmModelCopy (source->modelPSF);
+
+        // extend source radius as needed
+        psphotCheckRadiusPSF (readout, source, PSF);
+
+        x = PSF->params->data.F32[2];
+        y = PSF->params->data.F32[3];
+
+        // fit PSF model (set/unset the pixel mask)
+        psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "OR", PM_MASK_MARK);
+        pmSourceFitModel (source, PSF, PM_SOURCE_FIT_NORM);
+        psImageKeepCircle (source->mask, x, y, PSF->radiusFit, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+        // re-subtract PSF for object, leave local sky
+        pmModelSub (source->pixels, source->mask, source->modelPSF, false, false);
+
+        PARp = source->modelPSF->params->data.F32;
+        PARc = PSF->params->data.F32;
+        fprintf (f, "%7.1f %7.1f %9.2f %9.2f %10.3f\n", PARp[2], PARp[3], PARp[1], PARc[1], source->moments->dSky);
+        Nfit ++;
+    }
+    fclose (f);
+    psLogMsg ("psphot", 3, "measure PSF weighting bias for %d objects: %f sec\n", Nfit, psTimerMark ("psphot"));
+
+    return (true);
+}
Index: /branches/ipp-1-X-branches/rel-0-9/simtest/.cvsignore
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/simtest/.cvsignore	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/simtest/.cvsignore	(revision 21632)
@@ -0,0 +1,9 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+configure
+install-sh
+missing
Index: /branches/ipp-1-X-branches/rel-0-9/simtest/Makefile.am
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/simtest/Makefile.am	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/simtest/Makefile.am	(revision 21632)
@@ -0,0 +1,15 @@
+
+install_files = \
+	sim.pro \
+	images.pro
+
+installdir = $(datadir)/pantasks/modules
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir) 
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/ipp-1-X-branches/rel-0-9/simtest/autogen.sh
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/simtest/autogen.sh	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/simtest/autogen.sh	(revision 21632)
@@ -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=simtest
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+#($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+#        DIE=1
+#}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+#($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+#        DIE=1
+#}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+#$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+#$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/ipp-1-X-branches/rel-0-9/simtest/configure.ac
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/simtest/configure.ac	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/simtest/configure.ac	(revision 21632)
@@ -0,0 +1,17 @@
+AC_PREREQ(2.59)
+
+AC_INIT([simtest], [0.9.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([sim.pro])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_MAINTAINER_MODE
+
+AC_PROG_INSTALL
+
+MODULE_DIR="${datadir}/pantasks/modules"
+AC_SUBST(MODULE_DIR)
+
+AC_CONFIG_FILES([
+  Makefile
+])
+AC_OUTPUT
Index: /branches/ipp-1-X-branches/rel-0-9/simtest/images.pro
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/simtest/images.pro	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/simtest/images.pro	(revision 21632)
@@ -0,0 +1,210 @@
+
+# set global parameters for the simulations
+macro mkimages.init
+ if ($0 != 7)
+  echo "USAGE: mkimage.empty (Nx) (Ny) (RDNOISE) (SKY) (Nstars) (psfSigma)"
+  break
+ end
+
+ $mkiNx = $1
+ $mkiNy = $2
+ $mkiRD = $3
+ $mkiSKY = $4
+ $mkiPSFsigma = $6
+
+ gaussdev mki_noisev {$mkiNx*$mkiNy} 0.0 1.0
+ dimenup mki_noisev mki_noisei $mkiNx $mkiNy
+ mcreate blank $mkiNx $mkiNy
+end
+
+# generate a fake image with bias and sky, no stars
+macro mkimage.empty
+ if ($0 != 2)
+  echo "USAGE: mkimage.empty (buffer)"
+  break
+ end
+
+ set $1 = blank + $mkiRD*mki_noisei + sqrt($mkiSKY)*mki_noisei + $mkiSKY
+end
+
+# set global parameters for the simulations
+macro mkimages.stars
+ if ($0 != 7)
+  echo "USAGE: mkimage.empty (Nx) (Ny) (RDNOISE) (SKY) (Nstars) (psfSigma)"
+  break
+ end
+
+ $mkiNx = $1
+ $mkiNy = $2
+ $mkiRD = $3
+ $mkiSKY = $4
+ $mkiPSFsigma = $6
+
+ gaussdev mki_noisev {$mkiNx*$mkiNy} 0.0 1.0
+ dimenup mki_noisev mki_noisei $mkiNx $mkiNy
+ mcreate blank $mkiNx $mkiNy
+
+ # mkstars $5 0.6
+ mkstars $5 0.4
+ # mkstars $5 0.3
+end
+
+# generate a fake image with bias, sky, stars, gaussian PFS
+macro mkimage.stars.gauss
+ if ($0 != 2)
+  echo "USAGE: mkimage.stars (buffer)"
+  break
+ end
+
+ set _tmp.stars = mki_stars_image
+
+ mkimage.smooth _tmp.stars _tmp.smooth $mkiPSFsigma mkgauss
+
+ # noise sigma = sqrt (_tmp.smooth + $mkiRD^2 + $mkiSKY)
+ set $1 = _tmp.smooth + $mkiSKY + sqrt(_tmp.smooth + $mkiSKY + $mkiRD^2)*mki_noisei
+end
+
+# generate a fake image with bias, sky, stars, gaussian PFS
+macro mkimage.stars.power
+ if ($0 != 2)
+  echo "USAGE: mkimage.stars (buffer)"
+  break
+ end
+
+ set _tmp.stars = mki_stars_image
+
+ mkimage.smooth _tmp.stars _tmp.smooth $mkiPSFsigma mkpower
+
+ # noise sigma = sqrt (_tmp.smooth + $mkiRD^2 + $mkiSKY)
+ set $1 = _tmp.smooth + $mkiSKY + sqrt(_tmp.smooth + $mkiSKY + $mkiRD^2)*mki_noisei
+end
+
+# generate a fake image with bias, sky, stars, gaussian PFS
+macro mkimage.stars.wing
+ if ($0 != 2)
+  echo "USAGE: mkimage.stars (buffer)"
+  break
+ end
+
+ set _tmp.stars = mki_stars_image
+
+ mkimage.smooth _tmp.stars _tmp.smooth $mkiPSFsigma mkwing
+
+ # noise sigma = sqrt (_tmp.smooth + $mkiRD^2 + $mkiSKY)
+ set $1 = _tmp.smooth + $mkiSKY + sqrt(_tmp.smooth + $mkiSKY + $mkiRD^2)*mki_noisei
+end
+
+# generate the vectors of star coordinates, fluxes and magnitudes
+macro mkstars
+ if ($0 != 3)
+   echo "USAGE: mkstars (Nstars) (alpha)"
+   break
+ end
+
+ local sigma alpha Nstars
+ $Nstars = $1
+ $alpha = $2
+ # alpha is dlogN/dmag = 0.6 for N ~ S^-3/2
+
+ # $sigma is flux of star with S/N = 1 (sigma_sky * 4pi sigma_psf^2)
+ $sigma = sqrt($mkiRD^2 + $mkiSKY) * 4*3.1416*$mkiPSFsigma^2
+ 
+ # faintest magnitudes:
+ # include stars down to 1 sigma
+ $M1 = -2.5*log($sigma)
+
+ # brightest magnitude in sim
+ $M0 = $M1 - log($alpha*$Nstars/0.43)/$alpha
+
+ create _tmp 0 $Nstars
+ set mki_stars_x = $mkiNx * rnd(_tmp)
+ set mki_stars_y = $mkiNy * rnd(_tmp)
+
+ # random variable between 0 and Nstars
+ set _tmpvar = $Nstars * rnd(_tmp)
+
+ # random mags drawn from random variable
+ set mki_stars_mag = $M0 + log(_tmpvar*$alpha/0.43)/$alpha
+
+ set mki_stars_flux = ten(-0.4*mki_stars_mag)
+ sort mki_stars_mag mki_stars_flux mki_stars_x mki_stars_y
+
+ # insert the stars
+ mcreate mki_stars_image $mkiNx $mkiNy
+ for i 0 $Nstars
+   zap mki_stars_image mki_stars_x[$i] mki_stars_y[$i] 1 1 -v mki_stars_flux[$i]
+ end
+end
+
+####
+macro mkimage.smooth
+ if ($0 != 5)
+   echo "USAGE: smooth (in) (out) (sigma) (func)"
+   break
+ end
+ fft2d $1 0 to sm_I_r sm_I_i
+ set sm_g = zero($1)
+ keyword sm_g NAXIS1 SMnx
+ keyword sm_g NAXIS2 SMny
+ $4 sm_g $3 -c 0 0
+ $4 sm_g $3 -c $SMnx 0
+ $4 sm_g $3 -c 0 $SMny
+ $4 sm_g $3 -c $SMnx $SMny
+ stats -q sm_g - - - -
+ set sm_g = sm_g/$TOTAL
+ fft2d sm_g 0 to sm_G_r sm_G_i
+ set sm_r = sm_G_r*sm_I_r - sm_G_i*sm_I_i
+ set sm_i = sm_G_r*sm_I_i + sm_G_i*sm_I_r
+ fft2d -inverse sm_r sm_i to sm_I_r sm_I_i
+
+ set $2 = sm_I_r*$SMnx*$SMny*$SMnx*$SMny
+
+ if (1)
+  delete sm_I_r 
+  delete sm_I_i
+  delete sm_G_r 
+  delete sm_G_i
+  delete sm_r
+  delete sm_i
+  delete sm_g
+ end
+end
+
+macro mkpower
+ if ($0 != 6)
+  echo "USAGE: mkpower (buffer) (sigma) -c X Y"
+  break
+ end
+
+ delete -q tmp
+
+ keyword $1 NAXIS1 tmpnx
+ keyword $1 NAXIS2 tmpny
+ mcreate tmp $tmpnx $tmpny
+ set tmpx = xramp(tmp) - $4
+ set tmpy = yramp(tmp) - $5
+
+# $sigma_y = $2 * $AxisRatio
+# $sigma_xy = dsin(2*$Theta)*(
+
+ set tmpz = tmpx^2 / (2 * $2^2) + tmpy^2 / (2 * $2^2)
+ set $1 = $1 + 1 / (1 + tmpz + tmpz^2.5)
+end
+
+macro mkwing
+ if ($0 != 6)
+  echo "USAGE: mkpower (buffer) (sigma) -c X Y"
+  break
+ end
+
+ delete -q tmp
+
+ keyword $1 NAXIS1 tmpnx
+ keyword $1 NAXIS2 tmpny
+ mcreate tmp $tmpnx $tmpny
+ set tmpx = xramp(tmp) - $4
+ set tmpy = yramp(tmp) - $5
+
+ set tmpz = tmpx^2 / (2 * $2^2) + tmpy^2 / (2 * $2^2)
+ set $1 = $1 + 1 / (1 + tmpz + tmpz^2)
+end
Index: /branches/ipp-1-X-branches/rel-0-9/simtest/sim.pro
===================================================================
--- /branches/ipp-1-X-branches/rel-0-9/simtest/sim.pro	(revision 21632)
+++ /branches/ipp-1-X-branches/rel-0-9/simtest/sim.pro	(revision 21632)
@@ -0,0 +1,262 @@
+### Generate simulated test data for testing the Image Processing Pipeline
+
+module images.pro
+
+$flatlevel = 1000
+$biaslevel = 500
+$skyrate  = 200
+$darkrate = 100
+$shutter = 0.5
+
+macro cleanup
+  exec rm -f *.fit
+  exec rm -f *.jpg
+  exec rm -f *.fits
+  exec rm -rf logs
+end
+
+$simdir = `ipp_datapath.pl path://SIMTEST`
+
+macro init
+  exec pxadmin -delete -dbname simtest
+  exec pxadmin -create -dbname simtest
+  exec mkdir -p $simdir/raw
+  exec mkdir -p $simdir/proc
+  mkbiases
+  mkdarks
+  mkshutter
+  mkflats
+  mkobjects
+  exec ipp_serial_inject.pl $simdir/raw/*.fits --workdir path://SIMTEST/proc --dbname simtest
+end
+
+### create a bias detrun:
+# exec dettool -pretend -definebyquery -inst SIMTEST -filter NONE -det_type bias -select_exp_type bias -workdir path://SIMTEST/mkbias.0 -dbname simtest
+
+### If something goes wrong:
+# exec dettool -updatedetrun -det_id 1 -state stop -dbname simtest
+
+### create a dark detrun:
+# exec dettool -pretend -definebyquery -inst SIMTEST -filter NONE -det_type dark -select_exp_type dark -workdir path://SIMTEST/mkdark.0 -dbname simtest
+
+### create a shutter detrun:
+# exec dettool -pretend -definebyquery -inst SIMTEST -filter NONE -det_type shutter -select_exp_type shutter -workdir path://SIMTEST/mkshutter.0 -dbname simtest
+
+### create a flat detrun:
+# exec dettool -pretend -definebyquery -inst SIMTEST -filter B -det_type flat -select_exp_type flat -select_filter B -workdir path://SIMTEST/mkflat.0 -dbname simtest 
+# exec dettool -pretend -definebyquery -inst SIMTEST -filter V -det_type flat -select_exp_type flat -select_filter V -workdir path://SIMTEST/mkflat.1 -dbname simtest 
+
+# generate a set of fake biases
+macro mkbiases
+  echo "generating 10 bias images"
+  for i 0 10
+    mkimages.init 1024 1024 5 1000 0 2.0
+    set bias = blank + $mkiRD*mki_noisei + $biaslevel
+    keyword bias AIRMASS  -wf {1 + 0.1*$i}
+    keyword bias FILTER   -w NONE
+    keyword bias POSANGLE -wf 0.0
+    keyword bias RA       -wf 0.0
+    keyword bias DEC      -wf 0.0
+    keyword bias EXPTIME  -wf 0.0
+    keyword bias DARKTIME -wf 0.0
+    keyword bias DATE-OBS -w "2006/10/01"
+    sprintf time "00:00:%02d" $i
+    keyword bias UTC-OBS  -w $time
+    keyword bias CCDSUM   -w "1 1"
+    keyword bias TELESCOP -w simscope
+    keyword bias INSTRUME -w SIMTEST
+    keyword bias OBSTYPE  -w BIAS
+    keyword bias OBJECT   -w BIAS
+    sprintf name "$simdir/raw/bias.%02d.fits" $i
+    keyword bias GAIN     -wf 1.0
+    keyword bias RDNOISE  -wf $mkiRD
+    wd bias $name
+  end
+end
+
+macro mkdarks
+  echo "generating 20 dark images"
+  for i 0 20
+    $exptime = 3 * ($i + 1)
+    mkimages.init 1024 1024 5 1000 0 2.0
+    set dark = blank + sqrt($mkiRD*$mkiRD + $darkrate*$exptime)*mki_noisei + $darkrate*$exptime + $biaslevel
+    keyword dark AIRMASS  -wf {1 + 0.1*$i}
+    keyword dark FILTER   -w  NONE
+    keyword dark POSANGLE -wf 0.0
+    keyword dark RA       -wf 0.0
+    keyword dark DEC      -wf 0.0
+    keyword dark EXPTIME  -wf $exptime
+    keyword dark DARKTIME -wf $exptime
+    keyword dark DATE-OBS -w "2006/10/01"
+    sprintf time "00:01:%02d" $i
+    keyword dark UTC-OBS  -w $time
+    keyword dark CCDSUM   -w "1 1"
+    keyword dark TELESCOP -w simscope
+    keyword dark INSTRUME -w SIMTEST
+    keyword dark OBSTYPE  -w DARK
+    keyword dark OBJECT   -w DARK
+    keyword dark GAIN     -wf 1.0
+    keyword dark RDNOISE  -wf $mkiRD
+    sprintf name "$simdir/raw/dark.%02d.fits" $i
+    wd dark $name
+  end  
+end
+
+macro mkshutter
+  echo "generating 10 shutter images"
+  for i 0 10
+    $exptime = 3 * ($i + 1)
+    $opentime = $exptime - $shutter
+    mkimages.init 1024 1024 5 1000 0 2.0
+    set shutter = blank + sqrt($mkiRD*$mkiRD + $darkrate*$exptime + $flatlevel*$opentime)*mki_noisei + $flatlevel*$opentime + $darkrate*$exptime + $biaslevel
+    keyword shutter AIRMASS  -wf 1.0
+    keyword shutter FILTER   -w  NONE
+    keyword shutter POSANGLE -wf 0.0
+    keyword shutter RA       -wf 0.0
+    keyword shutter DEC      -wf 0.0
+    keyword shutter EXPTIME  -wf $exptime
+    keyword shutter DARKTIME -wf $exptime
+    keyword shutter DATE-OBS -w "2006/10/01"
+    sprintf time "00:01:%02d" $i
+    keyword shutter UTC-OBS  -w $time
+    keyword shutter CCDSUM   -w "1 1"
+    keyword shutter TELESCOP -w simscope
+    keyword shutter INSTRUME -w SIMTEST
+    keyword shutter OBSTYPE  -w SHUTTER
+    keyword shutter OBJECT   -w SHUTTER
+    keyword shutter GAIN     -wf 1.0
+    keyword shutter RDNOISE  -wf $mkiRD
+    sprintf name "$simdir/raw/shutter.%02d.fits" $i
+    wd shutter $name
+  end
+end
+
+macro mkflats
+  echo "generating 20 flat images in 2 filters"
+  $exptime = 10.0
+  $opentime = $exptime - $shutter
+  for i 0 10
+    mkimages.init 1024 1024 5 1000 0 2.0
+    set flat = blank + sqrt($mkiRD*$mkiRD + $darkrate*$exptime + $flatlevel*$opentime)*mki_noisei + $darkrate*$exptime + $flatlevel*$opentime + $biaslevel
+    keyword flat AIRMASS  -wf {1 + 0.1*$i}
+    keyword flat FILTER   -w  V
+    keyword flat POSANGLE -wf 0.0
+    keyword flat RA       -wf 0.0
+    keyword flat DEC      -wf 0.0
+    keyword flat EXPTIME  -wf $exptime
+    keyword flat DARKTIME -wf $exptime
+    keyword flat DATE-OBS -w "2006/10/01"
+    sprintf time "00:01:%02d" $i
+    keyword flat UTC-OBS  -w $time
+    keyword flat CCDSUM   -w "1 1"
+    keyword flat TELESCOP -w simscope
+    keyword flat INSTRUME -w SIMTEST
+    keyword flat OBSTYPE  -w FLAT
+    keyword flat OBJECT   -w FLAT
+    keyword flat GAIN     -wf 1.0
+    keyword flat RDNOISE  -wf $mkiRD
+    sprintf name "$simdir/raw/flat.%02d.fits" $i
+    wd flat $name
+  end  
+
+  for i 10 20
+    mkimages.init 1024 1024 5 1000 0 2.0
+    set flat = blank + sqrt($mkiRD*$mkiRD + $darkrate*$exptime + $flatlevel*$opentime)*mki_noisei + $darkrate*$exptime + $flatlevel*$opentime + $biaslevel
+    keyword flat AIRMASS  -wf {1 + 0.1*$i}
+    keyword flat FILTER   -w  B
+    keyword flat POSANGLE -wf 0.0
+    keyword flat RA       -wf 0.0
+    keyword flat DEC      -wf 0.0
+    keyword flat EXPTIME  -wf $exptime
+    keyword flat DARKTIME -wf $exptime
+    keyword flat DATE-OBS -w "2006/10/01"
+    sprintf time "00:02:%02d" $i
+    keyword flat UTC-OBS  -w $time
+    keyword flat CCDSUM   -w "1 1"
+    keyword flat TELESCOP -w simscope
+    keyword flat INSTRUME -w SIMTEST
+    keyword flat OBSTYPE  -w FLAT
+    keyword flat OBJECT   -w FLAT
+    keyword flat GAIN     -wf 1.0
+    keyword flat RDNOISE  -wf $mkiRD
+    sprintf name "$simdir/raw/flat.%02d.fits" $i
+    wd flat $name
+  end  
+end
+
+macro mkobjects
+  echo "generating 10 object images in 2 filters"
+  for i 0 5
+    $exptime = 3 * ($i + 1)
+    $opentime = $exptime - $shutter
+    mkimages.init 1024 1024 5 1000 0 2.0
+    set obj = blank + sqrt($mkiRD*$mkiRD + $darkrate*$exptime + $skyrate*$opentime)*mki_noisei + $darkrate*$exptime + $skyrate*$opentime + $biaslevel
+    keyword obj AIRMASS  -wf {1 + 0.1*$i}
+    keyword obj FILTER   -w  V
+    keyword obj POSANGLE -wf 0.0
+    keyword obj RA       -wf 0.0
+    keyword obj DEC      -wf 0.0
+    keyword obj EXPTIME  -wf $exptime
+    keyword obj DARKTIME -wf $exptime
+    keyword obj DATE-OBS -w "2006/10/01"
+    sprintf time "00:01:%02d" $i
+    keyword obj UTC-OBS  -w $time
+    keyword obj CCDSUM   -w "1 1"
+    keyword obj TELESCOP -w simscope
+    keyword obj INSTRUME -w SIMTEST
+    keyword obj OBSTYPE  -w OBJECT
+    keyword obj OBJECT   -w OBJECT
+    keyword obj GAIN     -wf 1.0
+    keyword obj RDNOISE  -wf $mkiRD
+    sprintf name "$simdir/raw/obj.%02d.fits" $i
+    wd obj $name
+  end  
+
+  for i 5 10
+    $exptime = 3 * ($i - 4)
+    $opentime = $exptime - $shutter
+    mkimages.init 1024 1024 5 1000 0 2.0
+    set obj = blank + sqrt($mkiRD*$mkiRD + $darkrate*$exptime + $skyrate*$opentime)*mki_noisei + $darkrate*$exptime + $skyrate*$opentime + $biaslevel
+    keyword obj AIRMASS  -wf {1 + 0.1*$i}
+    keyword obj FILTER   -w  B
+    keyword obj POSANGLE -wf 0.0
+    keyword obj RA       -wf 0.0
+    keyword obj DEC      -wf 0.0
+    keyword obj EXPTIME  -wf $exptime
+    keyword obj DARKTIME -wf $exptime
+    keyword obj DATE-OBS -w "2006/10/01"
+    sprintf time "00:02:%02d" $i
+    keyword obj UTC-OBS  -w $time
+    keyword obj CCDSUM   -w "1 1"
+    keyword obj TELESCOP -w simscope
+    keyword obj INSTRUME -w SIMTEST
+    keyword obj OBSTYPE  -w OBJECT
+    keyword obj OBJECT   -w OBJECT
+    keyword obj GAIN     -wf 1.0
+    keyword obj RDNOISE  -wf $mkiRD
+    sprintf name "$simdir/raw/obj.%02d.fits" $i
+    wd flat $name
+  end  
+end
+
+macro junk
+-definebyquery Optional arguments, with default values:
+    -det_type          ()                         define the type of detrend run (required)
+    -mode              (master)                   define the mode of this detrend run
+    -exp_type          ()                         search for exp_type
+    -inst              ()                         search for camera
+    -telescope         ()                         search for telescope
+    -filter            ()                         search for filter
+    -uri               ()                         search for uri
+    -set_exp_type      ()                         define exposure type
+    -set_filter        ()                         define filter
+    -set_airmass       (nan)            define airmass
+    -set_exp_time      (nan)            define exposure time
+    -set_ccd_temp      (nan)            define ccd tempature
+    -set_posang        (nan)            define rotator position angle
+    -set_object        ()                         define exposure object
+    -set_registered    (2006-12-13T22:40:18.5)    time detrend run was registered
+    -set_use_begin     ()                         start of detrend run applicable peroid
+    -set_use_end       ()                         end of detrend run applicable peroid
+    -pretend           (FALSE)                    print the exposures that would be included in the detrend run and exit
+end
