Index: /tags/psphot_dev_02/psphot/.cvsignore
===================================================================
--- /tags/psphot_dev_02/psphot/.cvsignore	(revision 5352)
+++ /tags/psphot_dev_02/psphot/.cvsignore	(revision 5352)
@@ -0,0 +1,1 @@
+bin
Index: /tags/psphot_dev_02/psphot/Makefile
===================================================================
--- /tags/psphot_dev_02/psphot/Makefile	(revision 5352)
+++ /tags/psphot_dev_02/psphot/Makefile	(revision 5352)
@@ -0,0 +1,115 @@
+default: psphot
+help:
+	@echo "USAGE: make psphot"
+
+CC      =       gcc
+SRC     =       src
+BIN     =       bin
+
+DESTBIN =       $(HOME)/src/bin/$(ARCH)
+
+LPSLIB  :=      $(shell pslib-config --libs)
+IPSLIB  :=      $(shell pslib-config --cflags)
+
+INCS	= 	$(IPSLIB)
+# LIBS	= 	-lpsmodule $(LPSLIB)
+LIBS	= 	$(LPSLIB)
+CFLAGS	=	$(INCS) -std=c99 -Wall -Werror -g
+# CFLAGS	=	$(INCS) -std=c99 -g
+LFLAGS	=	$(LIBS) 
+
+PSPHOT = \
+$(SRC)/psphot.$(ARCH).o            \
+$(SRC)/psphotArguments.$(ARCH).o   \
+$(SRC)/psphotSetup.$(ARCH).o	   \
+$(SRC)/psphotImageStats.$(ARCH).o  \
+$(SRC)/psphotSourceStats.$(ARCH).o \
+$(SRC)/psphotChoosePSF.$(ARCH).o   \
+$(SRC)/psphotApplyPSF.$(ARCH).o	   \
+$(SRC)/psphotFitGalaxies.$(ARCH).o \
+$(SRC)/psphotOutput.$(ARCH).o      \
+$(SRC)/psphotMarkPSF.$(ARCH).o     \
+$(SRC)/psphotSubtractPSF.$(ARCH).o \
+$(SRC)/psphotSortBySN.$(ARCH).o    \
+$(SRC)/psphotDefinePixels.$(ARCH).o \
+$(SRC)/psphotMagnitudes.$(ARCH).o  \
+$(SRC)/psLibUtils.$(ARCH).o	   \
+$(SRC)/psLine.$(ARCH).o		   \
+$(SRC)/psMinimize.$(ARCH).o	   \
+$(SRC)/psPolynomials.$(ARCH).o	   \
+$(SRC)/psEllipse.$(ARCH).o         \
+$(SRC)/psModulesUtils.$(ARCH).o	   \
+$(SRC)/pmPeaksSigmaLimit.$(ARCH).o \
+$(SRC)/pmPSF.$(ARCH).o             \
+$(SRC)/pmPSFtry.$(ARCH).o          \
+$(SRC)/psImageData.$(ARCH).o	   \
+$(SRC)/pmModelGroup.$(ARCH).o       \
+$(SRC)/pmObjects_EAM.$(ARCH).o
+
+MODELS = \
+$(SRC)/models/pmModel_GAUSS.c      \
+$(SRC)/models/pmModel_PGAUSS.c     \
+$(SRC)/models/pmModel_QGAUSS.c     \
+$(SRC)/models/pmModel_SGAUSS.c     \
+$(SRC)/models/pmModel_TGAUSS.c      
+
+$(SRC)/pmModelGroup.$(ARCH).o: $(MODELS)
+
+# deprecated
+
+FITSOURCE = \
+$(SRC)/fitsource.$(ARCH).o \
+$(SRC)/onesource.$(ARCH).o \
+$(SRC)/fs_args.$(ARCH).o \
+$(SRC)/psphot-utils.$(ARCH).o \
+$(SRC)/psPolynomials.$(ARCH).o \
+$(SRC)/psUtils.$(ARCH).o \
+$(SRC)/setup.$(ARCH).o \
+$(SRC)/LocalSky.$(ARCH).o
+
+psphot: $(BIN)/psphot.$(ARCH)
+$(BIN)/psphot.$(ARCH) : $(PSPHOT)
+$(PSPHOT): $(SRC)/psphot.h
+
+fitsource: $(BIN)/fitsource.$(ARCH)
+$(BIN)/fitsource.$(ARCH) : $(FITSOURCE)
+$(FITSOURCE): $(SRC)/psphot.h
+
+INSTALL = psphot
+
+# dependancy rules for binary code #########################
+.PRECIOUS: %.$(ARCH).o
+.PRECIOUS: $(BIN)/%.$(ARCH)
+
+%.$(ARCH).o : %.c
+	$(CC) $(CFLAGS) -c $< -o $@
+
+$(BIN)/%.$(ARCH) : $(SRC)/%.$(ARCH).o
+	@if [ ! -d $(BIN) ]; then mkdir -p $(BIN); fi
+	$(CC) $^ -o $@ $(LFLAGS)
+	@echo "done with $@"
+
+$(DESTBIN)/%: $(BIN)/%.$(ARCH)
+	@if [ ! -d $(DESTBIN) ]; then mkdir -p $(DESTBIN); fi
+	rm -f $(DESTBIN)/$*
+	cp $(BIN)/$*.$(ARCH) $(DESTBIN)/$*
+
+$(INSTALL): % : $(BIN)/%.$(ARCH)
+
+%.clean :
+	rm -f $(BIN)/$*.$(ARCH)
+	rm -f $(SRC)/*.$(ARCH).o
+
+%.install:
+	make $(DESTBIN)/$*
+
+# utilities #################################################
+
+install:
+	for i in $(INSTALL); do make $$i.install; done
+
+clean:	
+	rm -f $(BIN)/*.$(ARCH)
+	rm -f `find . -name "*.o"`
+	rm -f `find . -name "*~"`
+	rm -f `find . -name "#*"`
Index: /tags/psphot_dev_02/psphot/doc/notes.txt
===================================================================
--- /tags/psphot_dev_02/psphot/doc/notes.txt	(revision 5352)
+++ /tags/psphot_dev_02/psphot/doc/notes.txt	(revision 5352)
@@ -0,0 +1,244 @@
+
+Notes on psphot
+
+2005.09.06
+
+  I have built a working version of PSPhot using my own copy of the
+  pmObjects.[ch] files.  I need to minimize the difference between the
+  v0.7.0, v0.5.0, and my own version of pmObjects.c
+
+  additions to psLib based on the new version:
+  - extended the minimization to include parameter limits
+
+  changes v0.5.0 / v0.7.0 / EAM
+  - changed all ps* to pm*
+  - fixed naming for local static functions (eg modelFree not p_psModelFree)
+  - dropped all hard-coded model names (vs v0.7.0)
+  - using current psMemSetDellocator name
+  - added noise image to pmSource
+  - converted old asserts to new asserts (PS_.._CHECK..)
+  - add return from error in pmFindImagePeaks
+  - allow isItInThisRegion to have NULL region (true)
+  - fixed subImages to correspond to region definition (end is
+    exclusive) in LocalSky and SetPixelCircle
+  - dropped old, redundant code in pmSourceLocalSky
+  - fixed pmSourceMoments use of mask (0 is valid, not 1)
+  - added pmSourceMoments validity tests (numPixels, Sum, centroid shift)
+  - added correction for Sxy (XY/Sum - x*y)
+  - added pmSourcePSFClump function (and pmPSFClump structure)
+  - allow missing sources in pmSourcePSFClump (not all peaks yield
+    sources)
+  - moved pmSourceRoughClass metadata lookups out of inner loop
+  - added saturated pixel test using pmImageCountPixelMask
+  - various redefinitions of initial classes
+  - dropped old, redundant code in pmSourceSetPixelsCircle
+  - mask image set to type psU8 (was psF32)
+  - abstract object model functions for pmSourceModelGuess
+  - converted evalModel to a public function psModelEval, modified params
+  - pmSourceFitModel operates on a specified model
+  - pmSourceFitModel had option to apply/ignore PSF information
+  - pmSourceFitModel calculates yErr
+  - pmSourceFitModel calculates the covariance matrix
+  - added paramMask to pmSourceFitModel 
+  - pmSourceFitModel_EAM uses covar to carry in beta and param
+    limits...
+  - pmSourceFitModel requires solution to stay within image
+  - pmSourceFitModel calculates and saves chisq, nIter, nDOF
+  - pmSourceFitModel uses GaussNewtonDelta for frozen params
+  - added mask to pmSourceAdd, pmSourceSub
+  - model abstractions in pmSourceAdd, pmSourceSub
+  - fixed 'center' option in pmSourceAdd/Sub
+
+  - pmPSF params should be F64
+
+  lingering concerns:
+  - Polynomials and related functions are STILL messed up.  this is a
+    major priority!!!
+  - pmSourceFitModel calculates sqrt(var), psMinimize calculates sq(sqrt(var))
+  - does psImageAlloc zero the image?  if so, drop init routine
+    from pmSourcePSFClump
+  - pmSourceRoughClass uses RDNOISE, GAIN to calculate SN: should use
+    the source->noise image? part of pmSourceMoments?
+  - pmSourceMoments_EAM uses macro for checkRegion
+  - pmObjects_EAM.c uses some hardcoded mask values (LocalSky)
+  - why does LocalSky_EAM not need subImageMask->col0 = subImage->col0??
+  - psGetRowVectorFromImage should be in psLib (in psLibUtils)
+  - pmSourceSetPixelsCircle and pmSourceLocalSky probably could use
+    the psImageMaskRegion, etc functions.
+  - pmSourceSetPixelsCircle uses a hard-coded mask value
+  - define a mask registration process??
+  - pmSourceContour is using the model flux, not the image flux
+
+2005.09.05
+
+  I have a working version of PSPhot which handles PSF and galaxy
+  models.  I am tagging the module with an alpha version number. this
+  version compiles with psLib-0.5.0, at least my version of it.  My
+  current goal is to make it work with the current psLib head (nearly
+  0.7.0) with a mimimum of _EAM versions of functions.
+
+  the tag for the working version is dev_01
+
+2005.06.04
+
+  progress on psphot has moved along well.  At this point, the process
+  loads the image, finds the peaks, determines a best PSF model,
+  identifies objects which are consistent with that model, and
+  attempts to fit a galaxy model to the objects which are not
+  consistent with the model. 
+
+  a variety of minor issues remain, as well as some major issues. 
+
+  - use a proper noise image to keep the fits honest after other
+    objects have been subtracted.  
+
+  - define the masks in terms of a single mask image.  this image
+    could be provided initially by the user.   
+
+  - add noise enhancement (couple with mask marking subtraction)
+
+  - 
+
+2005.04.12
+
+- psPeak, psSource, etc: should be pmPeak, pmSource, etc
+- pmCullPeaks: should be pmCullImagePeaks
+
+- does pmSourceMoments need an image argument?
+
+- psLibInit / p_psTimeInit
+
+  we should not have to know about astronomy time concepts (ie, have a
+  timeConfig file) in order to use the time functions for basic work.
+  This means having a PS_TIME_UNIX which just works with the UNIX
+  clock and avoids all initialization issues.
+
+- psImageStats is much too slow for basic median
+  - this function does a complete sort on all pixel values.  we need
+  to use the histogram method (spec a different type of median)
+
+- psImageStats, ROBUST_MEDIAN fails
+- there are errors in psVectorStats for ROBUST MEAN, STDEV (& median?)
+  * this is ill-defined.  revise the ADD: too sensitive to the
+  binning, smoothing scales 
+
+- pmFindImagePeaks is not finding any peaks:
+  typo in interior row section: see bug 359
+  after this fix: PASS
+
+* p_psGetRowVectorFromImage : PASS
+* psGetRowVectorFromImage : PASS 
+- both have about the same time (~15 us +/- 5us) for 2048 pix on alala
+
+* pmFindVectorPeaks : PASS (returned row data in data.U32)
+
+- # include <string.h> : needed in psMetadata.c
+
+* MyListAddPeak / pmPeakAlloc are inconsistent wrt col,row vs x,y
+
+- why does psMetadataItemAllocV allocate a string of length
+  MAX_STRING_LENGTH for the comment, then use strncpy?
+
+  should be:
+
+    // set metadata item comment
+    if (comment == NULL) {
+        // Per SDRS, null isn't allowed, must use "" instead
+        metadataItem->comment = psStringCopy ("");
+    } else {
+        metadataItem->comment = psStringCopy (comment);
+    }
+
+psMetadataAdd -> psMetadataAddItem is all confused:
+ - we end up with two psMetadataItemAlloc calls, when only one should
+   be used
+ - psMetadataItemAlloc was doing the wrong thing if the incoming data
+   was NULL
+ - I think the logic in this block is wrong as well.  
+
+ * I fixed this in psMetadata.c
+
+2005.04.14 : 
+
+ - psImageStats is still much too slow
+
+ - pmObjects : changed to using psArray to carry peaks / sources,
+   rather than psList
+
+ - added function pmPeaksSubset to replace pmCullPeaks
+
+ - pmSourceLocalSky: added min/max fncs to force subimage to stay in
+   image
+
+ - Sx, Sy can go negative: have forced limit to 0,0
+
+ - pmSourceRoughClass: implemented sigmaX, sigmaY search for stellar
+   clump (uses pmComparePeakDescend)
+
+   - the search ignores the 0,0 pixel
+
+   - pmSourceRoughClass uses all detected peaks.  allow an exclusion
+     for peak > max, peak < min?
+
+   - need to push clump stats on the metadata
+
+   - the source classes are assigned so they are mutually exclusive 
+
+   - change PS_SOURCE_OTHER to STAR? 
+
+ - pmSourceSetPixelCircle:
+
+   - name should be pmSourceSetPixelsCircle (geometry could be an
+     option...)
+
+   - I cleaned the defined region to match the convensions of
+     pmSourceLocalSky
+
+   - truncate and force subimage to lie on image
+
+   - CheckRadius2 seems like an inefficient coding
+
+   - valid pixels have mask value of 1 (inverse of other funcs)
+
+ - pmSourceFitModel:
+
+   - num iterations is much too large
+
+   - 
+
+mrq:
+
+ - allocate alpha, beta arrays
+ - mrq2dcof (pars, alpha, beta)
+ - make guess pars', alpha', beta'
+ - mrq2dcof (pars', alpha', beta')
+ - if (chisq < old chisq), keep new guess
+
+
+2005.04.19:
+
+- psMinimizeLMChi2 has some serious problems.  I re-wrote it starting
+  from the mrqmin example in ohana.   after a variety little bugs, it
+  seems to work quite well.  the program 'modeltest.c' runs two
+  different gaussians through psMinimizeLMChi2, and gets the right
+  answer quite quickly.  The first pass at the implementation had a
+  variety of problems.  to avoid any worry about errors in the
+  psMatrix code, I implemented psGaussJordan, basing the code on the
+  elixir gaussj.c code.  this worked fine, and let me find the errors
+  elsewhere in the code. the LUD version now works just the same as
+  the GaussJ version.
+
+
+2005.04.21
+
+- psMinimizeLMChi2 does an excellent job now in most cases.  I am able
+  to run pmSourceFitModel on the PSF stars quite well.  I added in the
+  (nearly) correct errors (actually, just using sqrt(N), limiting to
+  N>=1).  I see one problem object, which is quite faint, and does not
+  converge well: I get very large values for x,y, Sx, Sy, etc.  I need
+  to think about constraining these fits from running way off scale.
+
+  fit:   0.123879 sec for 277 stars (gauss)
+  fit:   0.155940 sec for 277 stars (pgauss) (!)
+  fit:   0.153292 sec for 277 stars
+  (0.5 msec per star)
Index: /tags/psphot_dev_02/psphot/doc/psphot.txt
===================================================================
--- /tags/psphot_dev_02/psphot/doc/psphot.txt	(revision 5352)
+++ /tags/psphot_dev_02/psphot/doc/psphot.txt	(revision 5352)
@@ -0,0 +1,160 @@
+
+TBD priorities:
+
+- test psImageSmooth / remove psImageSmooth_EAM
+- unify pmSourceLocalSky / pmSourceLocalSky_EAM
+- unify pmSourceMoments_EAM / pmSourceMoments_EAM
+- merge with image data hierarchy stuff
+- better name for psPSF_Test
+
+Defined APIs:
+
+top-level psphot functions : these do not need to be in psModules
+  psMetadata  *psphotArguments ();
+  psImageData *psphotSetup ();
+  psStats     *psphotImageStats ();
+  psArray     *pmPeaksSigmaLimit ();
+  psArray     *psphotSourceStats ();
+  pmPSF       *psphotChoosePSF ();
+  bool         psphotApplyPSF ();
+  bool         psphotFitGalaxies ();
+  void         psphotOutput ();
+
+psphot-specific functions
+  bool         psphotMarkPSF ();
+  bool         psphotSubtractPSF ();
+  int          psphotSortBySN ();
+  int          psphotSaveImage ();
+
+** add the following entries to the psLibSDRS
+
+psLib extra utilities
+  bool         psTimerStart ();		- already in SDRS
+  void         psTimerFree (); 		- already in SDRS
+  bool         psTimerClear ();		- add to psLibSDRS
+  psF64        psTimerMark (); 		- already in SDRS
+  psS32        psLogArguments ();	- now: psArgumentVerbosity in SDRS
+  psS32        psTraceArguments ();	- now: psArgumentVerbosity in SDRS
+  int          psArgumentGet ();	- already in SDRS
+  int          psArgumentRemove ();	- already in SDRS
+  psVector    *psVectorCreate ();	- add to psLibSDRS
+  int          psImageCountPixelMask ();- add to psLibSDRS
+  psVector    *psGetRowVectorFromImage(); - add to psLibSDRS?
+  bool         p_psVectorPrintRow ();     - add to psLibSDRS?
+
+// basic image functions
+  bool         psImageInit ();		- already in SDRS
+  void         psImageSmooth_EAM ();    - test psLib version and drop
+
+// psLine functions
+  psLine      *psLineAlloc ();		- add to psLibSDRS
+  bool         psLineInit (); 		- add to psLibSDRS
+  bool         psLineAdd ();  		- add to psLibSDRS
+
+** compare with current implementation / update psLib
+   we need to define a mechanism to carry the beta values and the
+   parameter limits
+
+// minimize 
+  psBool       p_psMinLM_GuessABP_EAM ();
+  psBool       psMinimizeLMChi2_EAM();
+  psF64        p_psMinLM_dLinear ();  -- not included in pslib.h files
+
+** already in psLib : some need to be cleaned up by MHPCC
+
+// polynomial functions
+  psF64           Polynomial2DEval();
+  psImage        *psBuildSums2D();
+  psPolynomial2D *VectorFitPolynomial2DOrd_EAM();
+  psPolynomial2D *Polynomial2DAlloc();
+  void            psPolynomial2DDump ();
+  psPolynomial2D *RobustFit2D_nomask();
+  psPolynomial2D *RobustFit2D();
+  psVector       *Polynomial2DEvalVector();
+
+  psVector       *psBuildSums1D();
+  void            psPolynomial1DDump ();
+  psF64           Polynomial1DEval_EAM();
+  psVector       *Polynomial1DEvalVector_EAM();
+  psPolynomial1D *Polynomial1DAlloc();
+  psPolynomial1D *VectorFitPolynomial1DOrd_EAM();
+
+** make sure these are all in the psModules SDRS:
+
+pmObjects.h:
+  pmMoments           *pmMomentsAlloc();
+  pmModel             *pmModelAlloc();
+  pmSource            *pmSourceAlloc();
+  psVector            *pmFindVectorPeaks()
+  psArray             *pmFindImagePeaks()
+  psList              *pmCullPeaks()
+  pmSource            *pmSourceLocalSky()
+  bool                 pmSourceMoments()
+  pmPSFClump           pmSourcePSFClump()
+  bool                 pmSourceRoughClass()
+  bool                 pmSourceSetPixelsCircle()
+  pmModel             *pmSourceModelGuess()
+  psArray             *pmSourceContour()
+  bool                 pmSourceFitModel()
+  bool                 pmSourceAddModel()
+  bool                 pmSourceSubModel()
+  int                  pmModelParameterCount ();
+  char                *pmModelGetType ();
+  pmModelType          pmModelSetType ();
+  pmModelFunc          pmModelFunc_GetFunction ();
+  pmModelFlux          pmModelFlux_GetFunction ();
+  pmModelRadius        pmModelRadius_GetFunction ();
+  pmModelLimits        pmModelLimits_GetFunction ();
+  pmModelGuessFunc     pmModelGuessFunc_GetFunction ();
+  pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction ();
+  pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction ();
+
+model function abstractions:
+  typedef psMinimizeLMChi2Func pmModelFunc;
+  typedef psF64 (*pmModelFlux)(const psVector *params);
+  typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+  typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+  typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+  typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+  typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+conversions between elliptical shape representations
+  EllipseAxes  EllipseMomentsToAxes ();
+  EllipseShape EllipseAxesToShape ();
+  EllipseAxes  EllipseShapeToAxes ();
+
+output functions
+  bool         pmSourcesWriteText ();  	-- be careful about psImageData...
+  bool         pmSourcesWriteOBJ ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMP ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMF ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteSX ();    	-- be careful about psImageData...
+  int          pmSourcesDophotType ();
+  bool         pmPeaksWriteText ();
+  bool         pmMomentsWriteText ();
+  bool         pmModelWritePSFs ();
+  bool         pmModelWriteFLTs ();
+  bool         pmModelWriteNULLs ();
+
+// psf utilities
+  pmPSF       *pmPSFAlloc ();
+  pmPSF_Test  *pmPSF_TestAlloc ();
+  pmPSF_Test  *pmPSF_TestModel ();
+  bool         pmPSFFromModels ();
+  pmModel     *pmModelFromPSF ();
+  bool         pmSourcePhotometry ();
+  bool         pmPSFMetricModel ();
+
+// psModule extra utilities
+  bool         pmSourceFitModel_EAM();  -- requires updated version of psMinimize...
+  bool         pmSourceLocalSky_EAM (); -- compare with pmSourceLocalSky & choose
+  bool         pmSourceMoments_EAM();   -- compare with pmSourceMoments & choose
+  bool         pmSourceDefinePixels();  -- be careful about psImageData...
+  bool         pmModelFitStatus ();
+  int          pmSourceDophotType ();
+  psF32        pmConfigLookupF32 ();	-- merge with Paul's image container hierarchy
+
+** this needs to be confronted with the phase2 / image container hierarchy
+
+// psImageData functions
+  psImageData *psImageDataAlloc ();
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_GAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_GAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_GAUSS.c	(revision 5352)
@@ -0,0 +1,152 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_GAUSS(psVector *deriv,
+			const psVector *params,
+			const psVector *x)
+{
+    psF32 X  = x->data.F32[0] - params->data.F32[2];
+    psF32 Y  = x->data.F32[1] - params->data.F32[3];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 r  = exp(-z);
+    psF32 q  = params->data.F32[1]*r;
+    psF32 f  = q + params->data.F32[0];
+
+    if (deriv != NULL) {
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+psF64 pmModelFlux_GAUSS(const psVector *params)
+{
+    psF64 A1   = PS_SQR(params->data.F32[4]);
+    psF64 A2   = PS_SQR(params->data.F32[5]);
+    psF64 A3   = PS_SQR(params->data.F32[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 pmModelRadius_GAUSS  (const psVector *params, psF64 flux)
+{
+    
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1]) return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+// define the parameter limits
+bool pmModelLimits_GAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_GAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+bool pmModelFromPSF_GAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 7; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+// check the status of the fitted model
+bool pmModelFitStatus_GAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_PGAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_PGAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_PGAUSS.c	(revision 5352)
@@ -0,0 +1,167 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_PGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+    psF32 t  = 1 + z + z*z/2.0;
+    psF32 r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = PAR[1]*r
+        psF32 q = PAR[1]*r*r*t;
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+bool pmModelLimits_PGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+psF64 pmModelFlux_PGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + z + z*z/2 + z*z*z/6);
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = params->data.F32[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_PGAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1]) return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelGuess_PGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+bool pmModelFromPSF_PGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 7; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_PGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_QGAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_QGAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_QGAUSS.c	(revision 5352)
@@ -0,0 +1,197 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = 
+    params->data.F32[8] = 
+*****************************************************************************/
+
+/* XXX EAM : we need a way to have user-set values for fix parameters */
+# define QG_S1  2.5
+# define dQG_S1 1.5
+/* dQG_S1 is QG_S1 - 1.0 */
+
+psF32 pmModelFunc_QGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 r  = 1.0 / (1 + PAR[7]*z + pow(z, QG_S1));
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(PAR[7] + QG_S1*pow(z, dQG_S1));
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z;
+    }
+    return(f);
+}
+
+bool pmModelLimits_QGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.1;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+    params_max[0][0].data.F32[7] = 10.0;
+
+    return (TRUE);
+}
+
+bool pmModelGuess_QGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    params[7] = 1.0;
+    return(true);
+}
+
+psF64 pmModelFlux_QGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + PAR[7]*z + pow(z, QG_S1));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_QGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[4], 1.0 / PAR[5]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[1];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+	f = 1.0 / (1 + PAR[7]*z + pow(z, QG_S1));
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_QGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_QGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_RGAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_RGAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_RGAUSS.c	(revision 5352)
@@ -0,0 +1,151 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z + z^Npow)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+*****************************************************************************/
+
+psF64 psModelFunc_RGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    // psF32 FACT = 1 + 5*exp(-5*PAR[7]);
+    
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z + z*p);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(1 + PAR[7]*p);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -q*px*X*2;
+        deriv->data.F32[5] = -q*py*Y*2;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+
+        // deriv->data.F32[4] = -1.8*q*px*X*2;
+        // deriv->data.F32[5] = -1.8*q*py*Y*2;
+        // deriv->data.F32[6] = -1.5*q*X*Y;
+        // deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_RGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + z + pow(z, PAR[7]));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = params->data.F32[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_RGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f, p;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[4], 1.0 / PAR[5]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[1];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+	p = pow(z, PAR[7]);
+	f = 1.0 / (1 + z + p);
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_RGAUSS (psModel *model, psSource *source) {
+
+    psVector *params = model->params;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 2.0;
+    return(true);
+}
+
+bool psModelFromPSF_RGAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_SGAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_SGAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_SGAUSS.c	(revision 5352)
@@ -0,0 +1,342 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + St*z^Ntide)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+    params->data.F32[8] = St
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+psF64 psImageEllipseContour (EllipseAxes axes, double xc, double yc, psImage *image);
+psF64 p_psImageGetElementF64(psImage *a, int i, int j);
+
+psF32 pmModelFunc_SGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = PS_MAX((0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y), 1e-8);
+    // note that if z -> 0, dPAR[7] -> -inf
+    // also z^(PAR[7]-1) -> Inf 
+
+    psF32 pr = z*PAR[8];
+    psF32 pr3 = pr*pr*pr;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + pr*pr3);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(PAR[7]*p + 4*PAR[8]*pr3);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -2*t*log(z)*z*p;
+        deriv->data.F32[8] = -2*t*4*z*pr3;
+    }
+    return(f);
+}
+
+bool pmModelLimits_SGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (9, PS_TYPE_F32);
+    *params_min = psVectorAlloc (9, PS_TYPE_F32);
+    *params_max = psVectorAlloc (9, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+    beta_lim[0][0].data.F32[8] = 0.05;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.5;
+    params_min[0][0].data.F32[8] = 0.001;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +3.0;
+    params_max[0][0].data.F32[7] = 5.0;
+    params_max[0][0].data.F32[8] = 0.5;
+
+    return (TRUE);
+}
+
+// measure the flux for the elliptical contour
+psF64 psImageEllipseContour (EllipseAxes axes, double xc, double yc, psImage *image) {
+
+    double t, dt, ct, st, xo, yo, value;
+    int N, Nt, x, y;
+
+    // choose dt to uniformly divide contour, with ~1 pix spacing at most
+    dt = asin (1 / axes.minor);
+    Nt = (int)(2*M_PI / dt) + 1;
+    dt = 2*M_PI / Nt;
+
+    ct = cos(axes.theta);
+    st = sin(axes.theta);
+    xo = xc - image->col0;
+    yo = yc - image->row0;
+    
+    psVector *contour = psVectorAlloc (Nt, PS_TYPE_F32);
+    for (t = 0, N = 0; (t < 2*M_PI) && (N < Nt); t += dt) {
+	x = ct*axes.major*cos(t) + st*axes.minor*sin(t) + xo;
+	y = ct*axes.minor*sin(t) + st*axes.major*cos(t) + yo;
+	value = p_psImageGetElementF64(image, x, y);
+	if (isfinite(value)) {
+	    contour->data.F32[N] = value;
+	    N++;
+	}
+    }
+    contour->n = N;
+    // accept every pixel: double counting is not so problematic here...
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, contour, NULL, NULL, 0);
+    value = stats->sampleMedian;
+
+    psFree (stats);
+    psFree (contour);
+
+    return (value);
+}
+    
+bool pmModelGuess_SGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+# if (0)    
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+	params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+	params[7] = 0.5;
+    }
+# endif
+
+    params[7] = 1.8;
+    params[8] = 0.1;
+
+
+    return(true);
+}
+
+// XXX EAM : test version using flux contours to guess slope
+bool pmModelGuess_SGAUSS_HARD (pmModel *model, pmSource *source) {
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+    float f1, f2;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+	params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+	params[7] = 0.5;
+    }
+    params[8] = 0.1;
+
+    return(true);
+}
+
+psF64 pmModelFlux_SGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+      psF32 pr = PAR[8]*z;
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_SGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = EllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+	z = SQ(r);
+	pr = PAR[8]*z;
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	if (f < limit) break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+	fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_SGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_SGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+    EllipseAxes axes;
+    EllipseShape shape;
+  
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = EllipseShapeToAxes (shape);
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP += PS_SQR(dPAR[7] / PAR[7]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+    status &= (fabs(PAR[8]) < 0.5);
+    status &= (dPAR[8] < 0.1);
+    status &= (axes.major > 1.41);
+    status &= (axes.minor > 1.41);
+    status &= ((axes.major / axes.minor) < 5.0);
+    status &= (PAR[7] > 0.5);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_TGAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_TGAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_TGAUSS.c	(revision 5352)
@@ -0,0 +1,200 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = 
+    params->data.F32[8] = 
+*****************************************************************************/
+
+/* XXX EAM : we need a way to have user-set values for fix parameters */
+# define TG_S  2.60
+# define dTG_S 1.60
+# define TRF 0.50
+/* dTG_S is TG_S - 1.0 */
+
+psF32 pmModelFunc_TGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+    psF32 er = exp(TRF*z);
+
+    psF32 r  = 1.0 / (er + PAR[7]*z + pow(z, TG_S));    // (1/R)
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+	psF32 t = PAR[1]*r*r;	                         // df/dR
+	psF32 q = t*(TRF*er + PAR[7] + TG_S*pow(z, dTG_S));  // (df/dR)(dR/dz)
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+	deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z;
+    }
+    return(f);
+}
+
+bool pmModelLimits_TGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.1;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+    params_max[0][0].data.F32[7] = 10.0;
+
+    return (TRUE);
+}
+
+bool pmModelGuess_TGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    params[7] = 1.0;
+    return(true);
+}
+
+psF64 pmModelFlux_TGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        psF32 er = exp(TRF*z);
+	f = 1.0 / (er + PAR[7]*z + pow(z, TG_S));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_TGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[4], 1.0 / PAR[5]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[1];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+        psF32 er = exp(TRF*z);
+	f = 1.0 / (er + PAR[7]*z + pow(z, TG_S));
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_TGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_TGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_WAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_WAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_WAUSS.c	(revision 5352)
@@ -0,0 +1,100 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF64 psModelFunc_WAUSS(psVector *deriv,
+			const psVector *params,
+			const psVector *x)
+{
+    psF32 X = x->data.F32[0] - params->data.F32[2];
+    psF32 Y = x->data.F32[1] - params->data.F32[2];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 t = 0.5*z*z*(1.0 + params->data.F32[8]*z/3.0);
+    psF32 r = 1.0 / (1.0 + z + params->data.F32[7]*t); /* exp (-Z) */
+    psF32 f = params->data.F32[1]*r + params->data.F32[0];
+
+    if (deriv != NULL) {
+        // note difference from gaussian: q = params->data.F32[1]*r
+        psF32 q = params->data.F32[1]*r*r*(1.0 + params->data.F32[7]*z*(1.0 + params->data.F32[8]*z/2.0));
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -100.0*params->data.F32[1]*r*r*t;
+        deriv->data.F32[8] = -100.0*params->data.F32[1]*r*r*params->data.F32[7]*(z*z*z)/6.0;
+        // The values of 100 dampen the swing of params->data.F32[7,8] */
+    }
+    return(f);
+}
+
+// this is probably wrong since it uses the gauss integral 2 pi sigma^2
+psF64 psModelFlux_WAUSS(const psVector *params)
+{
+    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
+    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
+    psF64 A3   = params->data.F32[6];
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 psModelRadius_WAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1] <= 0) return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+bool psModelGuess_WAUSS (psModel *model, psSource *source) {
+
+    psVector *params = model->params;
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = sqrt(2.0) / source->moments->Sx;
+    params->data.F32[5] = sqrt(2.0) / source->moments->Sy;
+    params->data.F32[6] = source->moments->Sxy;
+    // XXX: What are these?
+    // params->data.F32[7] = B2;
+    // params->data.F32[8] = B3;
+    return(true);
+}
+
+bool psModelFromPSF_WAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/psphot_dev_02/psphot/src/models/pmModel_ZGAUSS.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/models/pmModel_ZGAUSS.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/models/pmModel_ZGAUSS.c	(revision 5352)
@@ -0,0 +1,158 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + PAR8*z^4)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+# define PAR8 0.1
+
+psF64 psModelFunc_ZGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 pr = PAR8*z;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + SQ(SQ(pr)));
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+	psF32 q = t*(PAR[7]*p + 4*PAR8*pr*pr*pr);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -q*px*X;
+        deriv->data.F32[5] = -q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*log(z)*z*p;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_ZGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    psF32 pr = PAR8*z;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_ZGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[1] <= 0) return (1.0);
+    if (flux >= PAR[1]) return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = EllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+	z = SQ(r);
+	pr = PAR8*z;
+	f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+	if (f < limit) break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+	fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_ZGAUSS (psModel *model, psSource *source) {
+
+    psVector *params = model->params;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 1.9;
+    return(true);
+}
+
+bool psModelFromPSF_ZGAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/psphot_dev_02/psphot/src/pmModelGroup.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmModelGroup.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmModelGroup.c	(revision 5352)
@@ -0,0 +1,111 @@
+# include "pmModelGroup.h"
+
+double hypot(double x, double y);
+double sqrt (double x);
+
+# include "models/pmModel_GAUSS.c"
+# include "models/pmModel_PGAUSS.c"
+# include "models/pmModel_QGAUSS.c"
+# include "models/pmModel_SGAUSS.c"
+# include "models/pmModel_TGAUSS.c"
+
+static pmModelGroup models[] = {
+    {"PS_MODEL_GAUSS",        7, pmModelFunc_GAUSS,   pmModelFlux_GAUSS,   pmModelRadius_GAUSS,   pmModelLimits_GAUSS,   pmModelGuess_GAUSS,  pmModelFromPSF_GAUSS,  pmModelFitStatus_GAUSS},
+    {"PS_MODEL_PGAUSS",       7, pmModelFunc_PGAUSS,  pmModelFlux_PGAUSS,  pmModelRadius_PGAUSS,  pmModelLimits_PGAUSS,  pmModelGuess_PGAUSS, pmModelFromPSF_PGAUSS, pmModelFitStatus_PGAUSS},
+    {"PS_MODEL_QGAUSS",       8, pmModelFunc_QGAUSS,  pmModelFlux_QGAUSS,  pmModelRadius_QGAUSS,  pmModelLimits_QGAUSS,  pmModelGuess_QGAUSS, pmModelFromPSF_QGAUSS, pmModelFitStatus_QGAUSS},
+    {"PS_MODEL_SGAUSS",       9, pmModelFunc_SGAUSS,  pmModelFlux_SGAUSS,  pmModelRadius_SGAUSS,  pmModelLimits_SGAUSS,  pmModelGuess_SGAUSS, pmModelFromPSF_SGAUSS, pmModelFitStatus_SGAUSS},
+    {"PS_MODEL_TGAUSS",       8, pmModelFunc_TGAUSS,  pmModelFlux_TGAUSS,  pmModelRadius_TGAUSS,  pmModelLimits_TGAUSS,  pmModelGuess_TGAUSS, pmModelFromPSF_TGAUSS, pmModelFitStatus_TGAUSS},
+};
+
+pmModelFunc pmModelFunc_GetFunction (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFunc);
+}
+
+pmModelFlux pmModelFlux_GetFunction (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFlux);
+}
+
+pmModelRadius pmModelRadius_GetFunction (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelRadius);
+}
+
+pmModelLimits pmModelLimits_GetFunction (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelLimits);
+}
+
+pmModelGuessFunc pmModelGuessFunc_GetFunction (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelGuessFunc);
+}
+
+pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFitStatusFunc);
+}
+
+pmModelFromPSFFunc pmModelFromPSFFunc_GetFunction (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFromPSFFunc);
+}
+
+psS32 pmModelParameterCount (pmModelType type) {
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (0);
+    }
+    return (models[type].nParams);
+}
+
+psS32 pmModelSetType (char *name) {
+
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    for (int i = 0; i < Nmodels; i++) {
+        if (!strcmp(models[i].name, name)) {
+            return (i);
+        }
+    }
+    return (-1);
+}
+
+char *pmModelGetType (pmModelType type) {
+
+    int Nmodels = sizeof (models) / sizeof (pmModelGroup);
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].name);
+}
Index: /tags/psphot_dev_02/psphot/src/pmModelGroup.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmModelGroup.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmModelGroup.h	(revision 5352)
@@ -0,0 +1,104 @@
+
+# ifndef PM_MODEL_GROUP_H 
+# define PM_MODEL_GROUP_H 
+
+# include "pmObjects_EAM.h"
+# include "pmPSF.h"
+
+/**
+ * 
+ *  The object model functions are defined to allow for the flexible addition
+ *  of new object models. Every object model, with parameters represented by
+ *  pmModel, has an associated set of functions which provide necessary support
+ *  operations. A set of abstract functions allow the programmer to select the
+ *  approriate function or property for a specific named object model.
+ * 
+ */
+
+/**
+ * 
+ *  This function is the model chi-square minimization function for this model.
+ * 
+ */
+typedef psMinimizeLMChi2Func pmModelFunc;
+
+
+/**
+ * 
+ * This function returns the integrated flux for the given model parameters.
+ */
+typedef psF64 (*pmModelFlux)(const psVector *params);
+
+
+/**
+ * 
+ *  This function returns the radius at which the given model and parameters
+ *  achieves the given flux.
+ * 
+ */
+typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+
+/**
+ * 
+ *  This function sets the model parameter limits vectors for the given model
+ * 
+ */
+typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+
+/**
+ * 
+ *  This function provides the model guess parameters based on the details of
+ *   the given source.
+ * 
+ */
+typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+
+
+/**
+ * 
+ *  This function constructs the PSF model for the given source based on the
+ *  supplied psf and the FLT model for the object.
+ * 
+ */
+typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+
+/**
+ * 
+ *  This function returns the success / failure status of the given model fit
+ * 
+ */
+typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+/**
+ * 
+ *  Each of the function types above has a corresponding function which returns
+ *  the function given the model type:
+ * 
+ */
+pmModelFunc          pmModelFunc_GetFunction (pmModelType type);
+pmModelFlux          pmModelFlux_GetFunction (pmModelType type);
+pmModelRadius        pmModelRadius_GetFunction (pmModelType type);
+pmModelLimits        pmModelLimits_GetFunction (pmModelType type);
+pmModelGuessFunc     pmModelGuessFunc_GetFunction (pmModelType type);
+pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction (pmModelType type);
+pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type);
+
+// pmModelGroup utility functions
+int                  pmModelParameterCount (pmModelType type);
+char                *pmModelGetType (pmModelType type);
+pmModelType          pmModelSetType (char *name);
+
+// structure to carry model group functions
+typedef struct {
+    char *name;
+    int nParams;
+    pmModelFunc          modelFunc;
+    pmModelFlux          modelFlux;
+    pmModelRadius        modelRadius;
+    pmModelLimits        modelLimits;
+    pmModelGuessFunc     modelGuessFunc;
+    pmModelFromPSFFunc   modelFromPSFFunc;
+    pmModelFitStatusFunc modelFitStatusFunc;
+} pmModelGroup;
+
+# endif
Index: /tags/psphot_dev_02/psphot/src/pmObjects_EAM.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmObjects_EAM.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmObjects_EAM.c	(revision 5352)
@@ -0,0 +1,1827 @@
+/** @file  pmObjects.c
+ *
+ *  This file will ...
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-10-17 19:04:33 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include<stdio.h>
+#include<math.h>
+#include<string.h>
+#include "pslib.h"
+#include "psConstants.h"
+#include "pmObjects_EAM.h"
+#include "pmModelGroup.h"
+
+
+/******************************************************************************
+pmPeakAlloc(): Allocate the pmPeak data structure and set appropriate members.
+*****************************************************************************/
+pmPeak *pmPeakAlloc(psS32 x,
+                    psS32 y,
+                    psF32 counts,
+                    pmPeakType class)
+{
+    pmPeak *tmp = (pmPeak *) psAlloc(sizeof(pmPeak));
+    tmp->x = x;
+    tmp->y = y;
+    tmp->counts = counts;
+    tmp->class = class;
+
+    return(tmp);
+}
+
+/******************************************************************************
+pmMomentsAlloc(): Allocate the pmMoments structure and initialize the members
+to zero.
+*****************************************************************************/
+pmMoments *pmMomentsAlloc()
+{
+    pmMoments *tmp = (pmMoments *) psAlloc(sizeof(pmMoments));
+    tmp->x = 0.0;
+    tmp->y = 0.0;
+    tmp->Sx = 0.0;
+    tmp->Sx = 0.0;
+    tmp->Sxy = 0.0;
+    tmp->Sum = 0.0;
+    tmp->Peak = 0.0;
+    tmp->Sky = 0.0;
+    tmp->nPixels = 0;
+
+    return(tmp);
+}
+
+static void modelFree(pmModel *tmp)
+{
+    psFree(tmp->params);
+    psFree(tmp->dparams);
+}
+
+/******************************************************************************
+pmModelAlloc(): Allocate the pmModel structure, along with its parameters,
+and initialize the type member.  Initialize the params to 0.0.
+XXX EAM: simplifying code with pmModelParameterCount
+*****************************************************************************/
+pmModel *pmModelAlloc(pmModelType type)
+{
+    pmModel *tmp = (pmModel *) psAlloc(sizeof(pmModel));
+
+    tmp->type = type;
+    tmp->chisq = 0.0;
+    tmp->nIter = 0;
+    psS32 Nparams = pmModelParameterCount (type);
+    if (Nparams == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return(NULL);
+    }
+	
+    tmp->params  = psVectorAlloc(Nparams, PS_TYPE_F32);
+    tmp->dparams = psVectorAlloc(Nparams, PS_TYPE_F32);
+
+    for (psS32 i = 0; i < tmp->params->n; i++) {
+        tmp->params->data.F32[i] = 0.0;
+        tmp->dparams->data.F32[i] = 0.0;
+    }
+
+    psMemSetDeallocator(tmp, (psFreeFunc) modelFree);
+    return(tmp);
+}
+
+/******************************************************************************
+XXX EAM : we can now free these pixels - memory ref is incremented now
+*****************************************************************************/
+static void sourceFree(pmSource *tmp)
+{
+    psFree(tmp->peak);
+    psFree(tmp->pixels);
+    psFree(tmp->weight);
+    psFree(tmp->mask);
+    psFree(tmp->moments);
+    psFree(tmp->modelPSF);
+    psFree(tmp->modelFLT);
+}
+
+/******************************************************************************
+pmSourceAlloc(): Allocate the pmSource structure and initialize its members
+to NULL.
+*****************************************************************************/
+pmSource *pmSourceAlloc()
+{
+    pmSource *tmp = (pmSource *) psAlloc(sizeof(pmSource));
+    tmp->peak = NULL;
+    tmp->pixels = NULL;
+    tmp->weight = NULL;
+    tmp->mask = NULL;
+    tmp->moments = NULL;
+    tmp->modelPSF = NULL;
+    tmp->modelFLT = NULL;
+    tmp->type = 0;
+    psMemSetDeallocator(tmp, (psFreeFunc) sourceFree);
+
+    return(tmp);
+}
+
+/******************************************************************************
+pmFindVectorPeaks(vector, threshold): Find all local peaks in the given vector
+above the given threshold.  Returns a vector of type PS_TYPE_U32 containing
+the location (x value) of all peaks.
+ 
+XXX: What types should be supported?  Only F32 is implemented.
+ 
+XXX: We currently step through the input vector twice; once to determine the
+size of the output vector, then to set the values of the output vector.
+Depending upon actual use, this may need to be optimized.
+*****************************************************************************/
+psVector *pmFindVectorPeaks(const psVector *vector,
+                            psF32 threshold)
+{
+    PS_ASSERT_VECTOR_NON_NULL(vector, NULL);
+    PS_ASSERT_VECTOR_NON_EMPTY(vector, NULL);
+    PS_ASSERT_VECTOR_TYPE(vector, PS_TYPE_F32, NULL);
+    int count = 0;
+    int n = vector->n;
+
+    //
+    // Special case: the input vector has a single element.
+    //
+    if (n == 1) {
+        psVector *tmpVector = NULL;
+        ;
+        if (vector->data.F32[0] > threshold) {
+            tmpVector = psVectorAlloc(1, PS_TYPE_U32);
+            tmpVector->data.U32[0] = 0;
+        } else {
+            tmpVector = psVectorAlloc(0, PS_TYPE_U32);
+        }
+        return(tmpVector);
+    }
+
+    //
+    // Determine if first pixel is a peak
+    //
+    if ((vector->data.F32[0] > vector->data.F32[1]) &&
+	(vector->data.F32[0] > threshold)) {
+        count++;
+    }
+
+    //
+    // Determine if interior pixels are peaks
+    //
+    for (psU32 i = 1; i < n-1 ; i++) {
+        if ((vector->data.F32[i] > vector->data.F32[i-1]) &&
+	    (vector->data.F32[i] > vector->data.F32[i+1]) &&
+	    (vector->data.F32[i] > threshold)) {
+            count++;
+        }
+    }
+
+    //
+    // Determine if last pixel is a peak
+    //
+    if ((vector->data.F32[n-1] > vector->data.F32[n-2]) &&
+	(vector->data.F32[n-1] > threshold)) {
+        count++;
+    }
+
+    //
+    // We know how many peaks exist, so we now allocate a psVector to store
+    // those peaks.
+    //
+    psVector *tmpVector = psVectorAlloc(count, PS_TYPE_U32);
+    count = 0;
+
+    //
+    // Determine if first pixel is a peak
+    //
+    if ((vector->data.F32[0] > vector->data.F32[1]) &&
+	(vector->data.F32[0] > threshold)) {
+        tmpVector->data.U32[count++] = 0;
+    }
+
+    //
+    // Determine if interior pixels are peaks
+    //
+    for (psU32 i = 1; i < (n-1) ; i++) {
+        if ((vector->data.F32[i] > vector->data.F32[i-1]) &&
+	    (vector->data.F32[i] > vector->data.F32[i+1]) &&
+	    (vector->data.F32[i] > threshold)) {
+            tmpVector->data.U32[count++] = i;
+        }
+    }
+
+    //
+    // Determine if last pixel is a peak
+    //
+    if ((vector->data.F32[n-1] > vector->data.F32[n-2]) &&
+	(vector->data.F32[n-1] > threshold)) {
+        tmpVector->data.U32[count++] = n-1;
+    }
+
+    return(tmpVector);
+}
+
+/******************************************************************************
+getRowVectorFromImage(): a private function which simply returns a
+psVector containing the specified row of data from the psImage.
+ 
+XXX: Is there a better way to do this?  
+XXX EAM: does this really need to alloc a new vector???
+*****************************************************************************/
+static psVector *getRowVectorFromImage(psImage *image,
+                                       psU32 row)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+
+    psVector *tmpVector = psVectorAlloc(image->numCols, PS_TYPE_F32);
+    for (psU32 col = 0; col < image->numCols ; col++) {
+        tmpVector->data.F32[col] = image->data.F32[row][col];
+    }
+    return(tmpVector);
+}
+
+/******************************************************************************
+myListAddPeak(): A private function which allocates a psArray, if the list
+argument is NULL, otherwise it adds the peak to that list.
+XXX EAM : changed the output to psArray
+XXX EAM : Switched row, col args
+XXX EAM : NOTE: this was changed in the call, so the new code is consistent
+*****************************************************************************/
+static psArray *myListAddPeak(psArray *list,
+                              psS32 row,
+                              psS32 col,
+                              psF32 counts,
+                              pmPeakType type)
+{
+    pmPeak *tmpPeak = pmPeakAlloc(col, row, counts, type);
+
+    if (list == NULL) {
+        list = psArrayAlloc(100);
+        list->n = 0;
+    }
+    psArrayAdd(list, 100, tmpPeak);
+    psFree (tmpPeak);
+    // XXX EAM : is this free appropriate?  (does psArrayAdd increment memory counter?)
+
+    return(list);
+}
+
+/******************************************************************************
+pmFindImagePeaks(image, threshold): Find all local peaks in the given psImage
+above the given threshold.  Returns a psArray containing location (x/y value)
+of all peaks.
+ 
+XXX: I'm not convinced the peak type definition in the SDRS is mutually
+exclusive.  Some peaks can have multiple types.  Edges for sure.  Also, a
+digonal line with the same value at each point will have a peak for every
+point on that line.
+ 
+XXX: This does not work if image has either a single row, or a single column.
+ 
+XXX: In the output psArray elements, should we use the image row/column offsets?
+     Currently, we do not.
+ 
+XXX: Merge with CVS 1.20.  This had the proper code for images with a single
+row or column.
+*****************************************************************************/
+psArray *pmFindImagePeaks(const psImage *image,
+                          psF32 threshold)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+    if ((image->numRows == 1) || (image->numCols == 1)) {
+        psError(PS_ERR_UNKNOWN, true, "Currently, input image must have at least 2 rows and 2 columns.");
+        return(NULL);
+    }
+    psVector *tmpRow = NULL;
+    psU32 col = 0;
+    psU32 row = 0;
+    psArray *list = NULL;
+
+    //
+    // Find peaks in row 0 only.
+    //
+    row = 0;
+    tmpRow = getRowVectorFromImage((psImage *) image, row);
+    psVector *row1 = pmFindVectorPeaks(tmpRow, threshold);
+
+    for (psU32 i = 0 ; i < row1->n ; i++ ) {
+        col = row1->data.U32[i];
+        //
+        // Determine if pixel (0,0) is a peak.
+        //
+        if (col == 0) {
+            if ( (image->data.F32[row][col] >  image->data.F32[row][col+1]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row+1][col]) &&
+		 (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+        } else if (col < (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row][col-1]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row][col+1]) &&
+		 (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row+1][col]) &&
+		 (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+
+        } else if (col == (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row][col-1]) &&
+		 (image->data.F32[row][col] > image->data.F32[row+1][col]) &&
+		 (image->data.F32[row][col] >= image->data.F32[row+1][col-1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "peak specified valid column range.");
+        }
+    }
+    psFree (tmpRow);
+    psFree (row1);
+
+    //
+    // Exit if this image has a single row.
+    //
+    if (image->numRows == 1) {
+        return(list);
+    }
+
+    //
+    // Find peaks in interior rows only.
+    //
+    for (row = 1 ; row < (image->numRows - 1) ; row++) {
+        tmpRow = getRowVectorFromImage((psImage *) image, row);
+        row1 = pmFindVectorPeaks(tmpRow, threshold);
+
+        // Step through all local peaks in this row.
+        for (psU32 i = 0 ; i < row1->n ; i++ ) {
+            pmPeakType myType = PM_PEAK_UNDEF;
+            col = row1->data.U32[i];
+
+            if (col == 0) {
+                // If col==0, then we can not read col-1 pixels
+                if ((image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row+1][col]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                    myType = PM_PEAK_EDGE;
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], myType);
+                }
+            } else if (col < (image->numCols - 1)) {
+                // This is an interior pixel
+                if ((image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+		    (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+		    (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row+1][col]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                    if (image->data.F32[row][col] > threshold) {
+                        if ((image->data.F32[row][col] > image->data.F32[row-1][col-1]) &&
+			    (image->data.F32[row][col] > image->data.F32[row-1][col]) &&
+			    (image->data.F32[row][col] > image->data.F32[row-1][col+1]) &&
+			    (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+			    (image->data.F32[row][col] > image->data.F32[row][col+1]) &&
+			    (image->data.F32[row][col] > image->data.F32[row+1][col-1]) &&
+			    (image->data.F32[row][col] > image->data.F32[row+1][col]) &&
+			    (image->data.F32[row][col] > image->data.F32[row+1][col+1])) {
+                            myType = PM_PEAK_LONE;
+                        }
+
+                        if ((image->data.F32[row][col] == image->data.F32[row-1][col-1]) ||
+			    (image->data.F32[row][col] == image->data.F32[row-1][col]) ||
+			    (image->data.F32[row][col] == image->data.F32[row-1][col+1]) ||
+			    (image->data.F32[row][col] == image->data.F32[row][col-1]) ||
+			    (image->data.F32[row][col] == image->data.F32[row][col+1]) ||
+			    (image->data.F32[row][col] == image->data.F32[row+1][col-1]) ||
+			    (image->data.F32[row][col] == image->data.F32[row+1][col]) ||
+			    (image->data.F32[row][col] == image->data.F32[row+1][col+1])) {
+                            myType = PM_PEAK_FLAT;
+                        }
+
+                        list = myListAddPeak(list, row, col, image->data.F32[row][col], myType);
+                    }
+                }
+            } else if (col == (image->numCols - 1)) {
+                // If col==numCols - 1, then we can not read col+1 pixels
+                if ((image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+		    (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+		    (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+		    (image->data.F32[row][col] >= image->data.F32[row+1][col])) {
+                    myType = PM_PEAK_EDGE;
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], myType);
+                }
+            } else {
+                psError(PS_ERR_UNKNOWN, true, "peak specified valid column range.");
+            }
+
+        }
+	psFree (tmpRow);
+	psFree (row1);
+    }
+
+    //
+    // Find peaks in the last row only.
+    //
+    row = image->numRows - 1;
+    tmpRow = getRowVectorFromImage((psImage *) image, row);
+    row1 = pmFindVectorPeaks(tmpRow, threshold);
+    for (psU32 i = 0 ; i < row1->n ; i++ ) {
+        col = row1->data.U32[i];
+        if (col == 0) {
+            if ( (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+		 (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+        } else if (col < (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+		 (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row][col-1]) &&
+		 (image->data.F32[row][col] >= image->data.F32[row][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+
+        } else if (col == (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+		 (image->data.F32[row][col] >  image->data.F32[row][col-1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row, col, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "peak specified valid colum range.");
+        }
+    }
+    psFree (tmpRow);
+    psFree (row1);
+    return(list);
+}
+
+// XXX: Macro this.
+static bool isItInThisRegion(const psRegion *valid,
+                             psS32 x,
+                             psS32 y)
+{
+
+    // XXX EAM this function should check the status of valid
+    if (valid == NULL) return (true);
+
+    if ((x >= valid->x0) &&
+	(x <= valid->x1) &&
+	(y >= valid->y0) &&
+	(y <= valid->y1)) {
+	return(true);
+    }
+
+    return(false);
+}
+
+
+/******************************************************************************
+psCullPeaks(peaks, maxValue, valid): eliminate peaks from the psArray that have
+a peak value above the given maximum, or fall outside the valid region.
+ 
+XXX: Should the sky value be used when comparing the maximum?
+ 
+XXX: warning message if valid is NULL?
+ 
+XXX: changed API to create a NEW output psArray (should change name as well)
+ 
+XXX: Do we free the psList elements of those culled peaks?
+
+XXX EAM : do we still need pmCullPeaks, or only pmPeaksSubset?
+*****************************************************************************/
+psList *pmCullPeaks(psList *peaks,
+                    psF32 maxValue,
+                    const psRegion *valid)
+{
+    PS_ASSERT_PTR_NON_NULL(peaks, NULL);
+
+    psListElem *tmpListElem = (psListElem *) peaks->head;
+    psS32 indexNum = 0;
+
+    //    printf("pmCullPeaks(): list size is %d\n", peaks->size);
+    while (tmpListElem != NULL) {
+        pmPeak *tmpPeak = (pmPeak *) tmpListElem->data;
+        if ((tmpPeak->counts > maxValue) ||
+	    ((valid != NULL) &&
+	     (true == isItInThisRegion(valid, tmpPeak->x, tmpPeak->y)))) {
+            psListRemoveData(peaks, (psPtr) tmpPeak);
+        }
+
+        indexNum++;
+        tmpListElem = tmpListElem->next;
+    }
+
+    return(peaks);
+}
+
+// XXX EAM: I changed this to return a new, subset array
+//          rather than alter the existing one
+psArray *pmPeaksSubset(psArray *peaks, psF32 maxValue, const psRegion *valid)
+{
+    PS_ASSERT_PTR_NON_NULL(peaks, NULL);
+
+    psArray *output = psArrayAlloc (200);
+    output->n = 0;
+
+    psTrace (".pmObjects.pmCullPeaks", 3, "list size is %d\n", peaks->n);
+
+    for (int i = 0; i < peaks->n; i++) {
+        pmPeak *tmpPeak = (pmPeak *) peaks->data[i];
+        if (tmpPeak->counts > maxValue)
+            continue;
+        if (valid != NULL) {
+            if (isItInThisRegion(valid, tmpPeak->x, tmpPeak->y))
+                continue;
+        }
+        psArrayAdd (output, 200, tmpPeak);
+    }
+    return(output);
+}
+
+/******************************************************************************
+pmSource *pmSourceLocalSky(image, peak, innerRadius, outerRadius): this
+routine creates a new pmSource data structure and sets the following members:
+    ->pmPeak
+    ->pmMoments->sky
+ 
+The sky value is set from the pixels in the square annulus surrounding the
+peak pixel.
+ 
+We simply create a subSet image and mask the inner pixels, then call
+psImageStats on that subImage+mask.
+ 
+XXX: The subImage has width of 1+2*outerRadius.  Verify with IfA.
+ 
+XXX: Use static data structures for:
+     subImage
+     subImageMask
+     myStats
+ 
+XXX: ensure that the inner and out radius fit in the actual image.  Should
+     we generate an error, or warning?  Currently an error.
+ 
+XXX: Sync with IfA on whether the peak x/y coords are data structure coords,
+     or they use the image row/column offsets.
+ 
+XXX: Should we simply set pmSource->peak = peak?  If so, should we increase
+the reference counter?  Or, should we copy the data structure?
+ 
+XXX: Currently the subimage always has an even number of rows/columns.  Is
+     this correct?  Since there is a center pixel, maybe it should have an
+     odd number of rows/columns.
+ 
+XXX: Use psTrace() for the print statements.
+ 
+XXX: Don't use separate structs for the subimage and mask.  Use the source->
+     members.
+*****************************************************************************/
+bool pmSourceLocalSky (pmSource *source,
+			   psStatsOptions statsOptions,
+			   psF32 Radius)
+{
+
+    psImage *image = source->pixels;
+    psImage *mask  = source->mask;
+    pmPeak *peak  = source->peak;
+    psRegion srcRegion;
+
+    // XXX EAM : psRegionXXX funcs should take value not ptr
+    srcRegion = psRegionForSquare (peak->x, peak->y, Radius);
+    srcRegion = psRegionForImage (mask, srcRegion);
+
+    psImageMaskRegion (mask, srcRegion, "OR", PSPHOT_MASK_MARKED);
+    psStats *myStats = psStatsAlloc(statsOptions);
+    myStats = psImageStats(myStats, image, mask, 0xff);
+    psImageMaskRegion (mask, srcRegion, "AND", ~PSPHOT_MASK_MARKED);
+
+    psF64 tmpF64;
+    p_psGetStatValue(myStats, &tmpF64);
+    psFree (myStats);
+
+    if (isnan(tmpF64)) return (false);
+    source->moments = pmMomentsAlloc();
+    source->moments->Sky = (psF32) tmpF64;
+    return (true);
+}
+
+/******************************************************************************
+bool checkRadius2(): private function which simply determines if the (x, y)
+point is within the radius of the specified peak.
+ 
+XXX: macro this for performance.
+XXX: this is rather inefficient - at least compute and compare against radius^2
+*****************************************************************************/
+static bool checkRadius2(psF32 xCenter,
+                         psF32 yCenter,
+                         psF32 radius,
+                         psF32 x,
+                         psF32 y)
+{
+    /// XXX EAM should compare with hypot (x,y) for speed
+    if ((PS_SQR(x - xCenter) + PS_SQR(y - yCenter)) < PS_SQR(radius)) {
+        return(true);
+    }
+
+    return(false);
+}
+
+/******************************************************************************
+pmSourceMoments(source, radius): this function takes a subImage defined in the
+pmSource data structure, along with the peak location, and determines the
+various moments associated with that peak.
+ 
+Requires the following to have been created:
+    pmSource
+    pmSource->peak
+    pmSource->pixels
+    pmSource->weight
+    pmSource->mask
+ 
+XXX: The peak calculations are done in image coords, not subImage coords.
+ 
+XXX EAM : this version clips input pixels on S/N
+XXX EAM : this version returns false for several reasons
+*****************************************************************************/
+# define VALID_RADIUS(X,Y,RAD2) (((RAD2) >= (PS_SQR(X) + PS_SQR(Y))) ? 1 : 0)
+
+bool pmSourceMoments(pmSource *source,
+		     psF32 radius)
+{
+    // PS_PTR_CHECK_NULL(source, NULL);
+    // PS_PTR_CHECK_NULL(source->peak, NULL);
+    // PS_PTR_CHECK_NULL(source->pixels, NULL);
+    // PS_PTR_CHECK_NULL(source->weight, NULL);
+    PS_FLOAT_COMPARE(0.0, radius, NULL);
+
+    //
+    // XXX: Verify the setting for sky if source->moments == NULL.
+    //
+    psF32 sky = 0.0;
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    } else {
+        sky = source->moments->Sky;
+    }
+
+    //
+    // Sum = SUM (z - sky)
+    // X1  = SUM (x - xc)*(z - sky)
+    // X2  = SUM (x - xc)^2 * (z - sky)
+    // XY  = SUM (x - xc)*(y - yc)*(z - sky)
+    //
+    psF32 peakPixel = -PS_MAX_F32;
+    psS32 numPixels = 0;
+    psF32 Sum = 0.0;
+    psF32 X1 = 0.0;
+    psF32 Y1 = 0.0;
+    psF32 X2 = 0.0;
+    psF32 Y2 = 0.0;
+    psF32 XY = 0.0;
+    psF32 x  = 0;
+    psF32 y  = 0;
+    psF32 R2 = PS_SQR(radius);
+
+    psF32 xPeak = source->peak->x;
+    psF32 yPeak = source->peak->y;
+
+    // XXX why do I get different results for these two methods of finding Sx?
+    // XXX Sx, Sy would be better measured if we clip pixels close to sky
+    // XXX Sx, Sy can still be imaginary, so we probably need to keep Sx^2?
+    // We loop through all pixels in this subimage (source->pixels), and for each
+    // pixel that is not masked, AND within the radius of the peak pixel, we
+    // proceed with the moments calculation.  need to do two loops for a
+    // numerically stable result.  first loop: get the sums.
+    // XXX EAM : mask == 0 is valid
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+        for (psS32 col = 0; col < source->pixels->numCols ; col++) {
+            if ((source->mask != NULL) && (source->mask->data.U8[row][col])) continue;
+
+	    psF32 xDiff = col + source->pixels->col0 - xPeak;
+	    psF32 yDiff = row + source->pixels->row0 - yPeak;
+
+	    // XXX EAM : calculate xDiff, yDiff up front;
+	    //           radius is just a function of (xDiff, yDiff)
+	    if (!VALID_RADIUS(xDiff, yDiff, R2)) continue;
+
+	    psF32 pDiff = source->pixels->data.F32[row][col] - sky;
+
+	    // XXX EAM : check for valid S/N in pixel
+	    // XXX EAM : should this limit be user-defined?
+	    if (pDiff / sqrt(source->weight->data.F32[row][col]) < 1) continue;
+	    
+	    Sum += pDiff;
+	    X1  += xDiff * pDiff;
+	    Y1  += yDiff * pDiff;
+	    XY  += xDiff * yDiff * pDiff;
+	    
+	    X2  += PS_SQR(xDiff) * pDiff;
+	    Y2  += PS_SQR(yDiff) * pDiff;
+	    
+	    peakPixel = PS_MAX (source->pixels->data.F32[row][col], peakPixel);
+	    numPixels++;
+        }
+    }
+    // XXX EAM - the limit is a bit arbitrary.  make it user defined?
+    if ((numPixels < 3) || (Sum <= 0)) {
+      psTrace (".psModules.pmSourceMoments", 5, "no valid pixels for source\n");
+      return (false);
+    }
+
+    psTrace (".psModules.pmSourceMoments", 5, 
+	     "sky: %f  Sum: %f  X1: %f  Y1: %f  X2: %f  Y2: %f  XY: %f  Npix: %d\n", 
+	     sky, Sum, X1, Y1, X2, Y2, XY, numPixels);
+
+    //
+    // first moment X  = X1/Sum + xc
+    // second moment X = sqrt (X2/Sum - (X1/Sum)^2)
+    // Sxy             = XY / Sum
+    //
+    x = X1/Sum;
+    y = Y1/Sum;
+    if ((fabs(x) > radius) || (fabs(y) > radius)) {
+      psTrace (".psModules.pmSourceMoments", 5, 
+	       "large centroid swing; invalid peak %d, %d\n", 
+	       source->peak->x, source->peak->y);
+      return (false);
+    }
+
+    source->moments->x = x + xPeak;
+    source->moments->y = y + yPeak;
+
+    // XXX EAM : Sxy needs to have x*y subtracted
+    source->moments->Sxy = XY/Sum - x*y;
+    source->moments->Sum = Sum;
+    source->moments->Peak = peakPixel;
+    source->moments->nPixels = numPixels;
+
+    // XXX EAM : these values can be negative, so we need to limit the range
+    source->moments->Sx = sqrt(PS_MAX(X2/Sum - PS_SQR(x), 0));
+    source->moments->Sy = sqrt(PS_MAX(Y2/Sum - PS_SQR(y), 0));
+
+    psTrace (".psModules.pmSourceMoments", 4, 
+	     "sky: %f  Sum: %f  x: %f  y: %f  Sx: %f  Sy: %f  Sxy: %f\n", 
+	     sky, Sum, source->moments->x, source->moments->y, 
+	     source->moments->Sx, source->moments->Sy, source->moments->Sxy);
+
+    return(true);
+}
+
+// XXX EAM : I used
+int pmComparePeakAscend (const void **a, const void **b)
+{
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 diff;
+
+    diff = A->counts - B->counts;
+    if (diff < FLT_EPSILON)
+        return (-1);
+    if (diff > FLT_EPSILON)
+        return (+1);
+    return (0);
+}
+
+int pmComparePeakDescend (const void **a, const void **b)
+{
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 diff;
+
+    diff = A->counts - B->counts;
+    if (diff < FLT_EPSILON)
+        return (+1);
+    if (diff > FLT_EPSILON)
+        return (-1);
+    return (0);
+}
+
+/******************************************************************************
+pmSourcePSFClump(source, metadata): Find the likely PSF clump in the 
+sigma-x, sigma-y plane. return 0,0 clump in case of error. 
+*****************************************************************************/
+
+// XXX EAM include a S/N cutoff in selecting the sources?
+pmPSFClump pmSourcePSFClump(psArray *sources, psMetadata *metadata)
+{
+
+# define NPIX 10
+# define SCALE 0.1
+
+    psArray *peaks  = NULL;
+    pmPSFClump emptyClump = {0.0, 0.0, 0.0, 0.0};
+    pmPSFClump psfClump = emptyClump;
+
+    PS_ASSERT_PTR_NON_NULL(sources, emptyClump);
+    PS_ASSERT_PTR_NON_NULL(metadata, emptyClump);
+
+    // find the sigmaX, sigmaY clump
+    {
+        psStats *stats  = NULL;
+        psImage *splane = NULL;
+        int binX, binY;
+
+        // construct a sigma-plane image
+	// psImageAlloc does zero the data
+        splane = psImageAlloc (NPIX/SCALE, NPIX/SCALE, PS_TYPE_F32);
+	for (int i = 0; i < splane->numRows; i++) {
+	    memset (splane->data.F32[i], 0, splane->numCols*sizeof(PS_TYPE_F32));
+	}
+
+        // place the sources in the sigma-plane image (ignore 0,0 values?)
+        for (psS32 i = 0 ; i < sources->n ; i++)
+        {
+            pmSource *tmpSrc = (pmSource *) sources->data[i];
+            if (tmpSrc == NULL) {
+		continue;
+	    }
+            if (tmpSrc->moments == NULL) {
+		continue;
+	    }
+
+            // Sx,Sy are limited at 0.  a peak at 0,0 is artificial
+            if ((fabs(tmpSrc->moments->Sx) < FLT_EPSILON) && (fabs(tmpSrc->moments->Sy) < FLT_EPSILON)) {
+                continue;
+            }
+
+            // for the moment, force splane dimensions to be 10x10 image pix
+            binX = tmpSrc->moments->Sx/SCALE;
+            if (binX < 0)
+                continue;
+            if (binX >= splane->numCols)
+                continue;
+
+            binY = tmpSrc->moments->Sy/SCALE;
+            if (binY < 0)
+                continue;
+            if (binY >= splane->numRows)
+                continue;
+
+            splane->data.F32[binY][binX] += 1.0;
+        }
+
+        // find the peak in this image
+        stats = psStatsAlloc (PS_STAT_MAX);
+        stats = psImageStats (stats, splane, NULL, 0);
+        peaks = pmFindImagePeaks (splane, stats[0].max / 2);
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump threshold is %f\n", stats[0].max/2);
+
+	psFree (splane);
+	psFree (stats);
+	
+    }
+    // XXX EAM : possible errors:
+    //           1) no peak in splane 
+    //           2) no significant peak in splane
+
+    // measure statistics on Sx, Sy if Sx, Sy within range of clump
+    {
+        pmPeak *clump;
+        psF32 minSx, maxSx;
+        psF32 minSy, maxSy;
+        psVector *tmpSx = NULL;
+        psVector *tmpSy = NULL;
+        psStats *stats  = NULL;
+
+        // XXX EAM : this lets us takes the single highest peak
+        psArraySort (peaks, pmComparePeakDescend);
+        clump = peaks->data[0];
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump is at %d, %d (%f)\n", clump->x, clump->y, clump->counts);
+
+        // define section window for clump
+        minSx = clump->x * SCALE - 0.2;
+        maxSx = clump->x * SCALE + 0.2;
+        minSy = clump->y * SCALE - 0.2;
+        maxSy = clump->y * SCALE + 0.2;
+
+        tmpSx = psVectorAlloc (sources->n, PS_TYPE_F32);
+        tmpSy = psVectorAlloc (sources->n, PS_TYPE_F32);
+        tmpSx->n = 0;
+        tmpSy->n = 0;
+
+        // XXX clip sources based on flux?
+        // create vectors with Sx, Sy values in window
+        for (psS32 i = 0 ; i < sources->n ; i++)
+        {
+            pmSource *tmpSrc = (pmSource *) sources->data[i];
+
+            if (tmpSrc->moments->Sx < minSx)
+                continue;
+            if (tmpSrc->moments->Sx > maxSx)
+                continue;
+            if (tmpSrc->moments->Sy < minSy)
+                continue;
+            if (tmpSrc->moments->Sy > maxSy)
+                continue;
+            tmpSx->data.F32[tmpSx->n] = tmpSrc->moments->Sx;
+            tmpSy->data.F32[tmpSy->n] = tmpSrc->moments->Sy;
+            tmpSx->n++;
+            tmpSy->n++;
+            if (tmpSx->n == tmpSx->nalloc) {
+                psVectorRealloc (tmpSx, tmpSx->nalloc + 100);
+                psVectorRealloc (tmpSy, tmpSy->nalloc + 100);
+            }
+        }
+
+        // measures stats of Sx, Sy
+        stats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+
+        stats = psVectorStats (stats, tmpSx, NULL, NULL, 0);
+        psfClump.X  = stats->clippedMean;
+        psfClump.dX = stats->clippedStdev;
+
+        stats = psVectorStats (stats, tmpSy, NULL, NULL, 0);
+        psfClump.Y  = stats->clippedMean;
+        psfClump.dY = stats->clippedStdev;
+
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump  X,  Y: %f, %f\n", psfClump.X, psfClump.Y);
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
+        // these values should be pushed on the metadata somewhere
+	
+	psFree (stats);
+	psFree (peaks);
+	psFree (tmpSx);
+	psFree (tmpSy);
+    }
+
+    return (psfClump);
+}
+
+/******************************************************************************
+pmSourceRoughClass(source, metadata): make a guess at the source
+classification.
+ 
+XXX: push the clump info into the metadata?
+
+XXX: How can this function ever return FALSE?
+
+XXX EAM : add the saturated mask value to metadata 
+*****************************************************************************/
+
+bool pmSourceRoughClass(psArray *sources, psMetadata *metadata, pmPSFClump clump)
+{
+
+    psBool rc = true;
+
+    int Nsat   	 = 0;
+    int Ngal   	 = 0;
+    int Nstar  	 = 0;
+    int Npsf   	 = 0;
+    int Ncr    	 = 0;
+    int Nsatstar = 0;
+    // psRegion allArray;
+
+    psVector *starsn = psVectorAlloc (sources->n, PS_TYPE_F32);
+    starsn->n = 0;
+
+    // check return status value (do these exist?)
+    bool status;
+    psF32 RDNOISE    = psMetadataLookupF32 (&status, metadata, "RDNOISE");
+    psF32 GAIN       = psMetadataLookupF32 (&status, metadata, "GAIN");
+    psF32 PSF_SN_LIM = psMetadataLookupF32 (&status, metadata, "PSF_SN_LIM");
+    // psF32 SATURATE = psMetadataLookupF32 (&status, metadata, "SATURATE");
+
+    // XXX allow clump size to be scaled relative to sigmas?
+    // make rough IDs based on clumpX,Y,DX,DY
+    for (psS32 i = 0 ; i < sources->n ; i++) {
+
+        pmSource *tmpSrc = (pmSource *) sources->data[i];
+
+        tmpSrc->peak->class = 0;
+
+        psF32 sigX = tmpSrc->moments->Sx;
+        psF32 sigY = tmpSrc->moments->Sy;
+
+	// calculate and save signal-to-noise estimates
+        psF32 S  = tmpSrc->moments->Sum;
+        psF32 A  = 4 * M_PI * sigX * sigY;
+        psF32 B  = tmpSrc->moments->Sky;
+        psF32 RT = sqrt(S + (A * B) + (A * PS_SQR(RDNOISE) / sqrt(GAIN)));
+        psF32 SN = (S * sqrt(GAIN) / RT);
+	tmpSrc->moments->SN = SN;
+
+	// XXX EAM : can we use the value of SATURATE if mask is NULL?
+	// XXX EAM *** serious errors in psImageCountPixelMask: inconsistent with psRegion and subimage
+	// allArray = psRegionSet (1, tmpSrc->mask->numCols, 1, tmpSrc->mask->numRows);
+	// int Nsatpix = psImageCountPixelMask (tmpSrc->mask, allArray, PSPHOT_MASK_SATURATED);
+	int Nsatpix = psImageCountPixelMask_EAM (tmpSrc->mask, PSPHOT_MASK_SATURATED);
+
+        // saturated star (size consistent with PSF or larger)
+	// Nsigma should be user-configured parameter
+	bool big = (sigX > (clump.X - clump.dX)) && (sigY > (clump.Y - clump.dY));
+        if ((Nsatpix > 1) && big) {
+            tmpSrc->type |= PM_SOURCE_SATSTAR;
+            Nsatstar ++;
+            continue;
+        }
+
+        // saturated object (not a star, eg bleed trails, hot pixels)
+        if (Nsatpix > 1) {
+            tmpSrc->type |= PM_SOURCE_SATURATED;
+            Nsat ++;
+            continue;
+        }
+
+        // likely defect (too small to be stellar) (push out to 3 sigma)
+	// low S/N objects which are small are probably stellar
+	// only set candidate defects if 
+        if ((sigX < 0.05) || (sigY < 0.05)) {
+            tmpSrc->type |= PM_SOURCE_DEFECT;
+            Ncr ++;
+            continue;
+        }
+
+        // likely unsaturated galaxy (too large to be stellar)
+        if ((sigX > (clump.X + 3*clump.dX)) || (sigY > (clump.Y + 3*clump.dY))) {
+            tmpSrc->type |= PM_SOURCE_GALAXY;
+            Ngal ++;
+            continue;
+        }
+
+        // the rest are probable stellar objects
+        starsn->data.F32[starsn->n] = SN;
+        starsn->n ++;
+        Nstar ++;
+
+        // PSF star (within 1.5 sigma of clump center
+        psF32 radius = hypot ((sigX-clump.X)/clump.dX, (sigY-clump.Y)/clump.dY);
+        if ((SN > PSF_SN_LIM) && (radius < 1.5)) {
+            tmpSrc->type |= PM_SOURCE_PSFSTAR;
+            Npsf ++;
+            continue;
+        }
+
+        // random type of star
+        tmpSrc->type |= PM_SOURCE_OTHER;
+    }
+
+    {
+        psStats *stats  = NULL;
+        stats = psStatsAlloc (PS_STAT_MIN | PS_STAT_MAX);
+        stats = psVectorStats (stats, starsn, NULL, NULL, 0);
+        psLogMsg ("pmObjects", 3, "SN range: %f - %f\n", stats[0].min, stats[0].max);
+	psFree (stats);
+	psFree (starsn);
+    }
+
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Nstar:    %3d\n", Nstar);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Npsf:     %3d\n", Npsf);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Ngal:     %3d\n", Ngal);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Nsatstar: %3d\n", Nsatstar);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Nsat:     %3d\n", Nsat);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Ncr:      %3d\n", Ncr);
+
+    return(rc);
+}
+
+/******************************************************************************
+pmSourceSetPixelsCircle(source, image, radius)
+ 
+XXX: Why boolean output?
+ 
+XXX: Why are we checking source->moments for NULL?  Should the circle be
+     centered on the centroid or the peak?
+ 
+XXX: The circle will have a diameter of (1+radius).  This is different from
+     the pmSourceSetLocal() function.
+
+XXX: this should probably use the psImageMaskCircle Function for consistency
+*****************************************************************************/
+bool pmSourceSetPixelsCircle(pmSource *source,
+                             const psImage *image,
+                             psF32 radius)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_FLOAT_COMPARE(0.0, radius, false);
+
+    //
+    // We define variables for code readability.
+    //
+    // XXX: Since the peak->xy coords are in image, not subImage coords,
+    // these variables should be renamed for clarity (imageCenterRow, etc).
+    //
+    psS32 radiusS32 = (psS32) radius;
+    psS32 SubImageCenterRow = source->peak->y;
+    psS32 SubImageCenterCol = source->peak->x;
+    // XXX EAM : for the circle to stay on the image
+    // XXX EAM : EndRow is *exclusive* of pixel region (ie, last pixel + 1)
+    psS32 SubImageStartRow  = PS_MAX (0, SubImageCenterRow - radiusS32);
+    psS32 SubImageEndRow    = PS_MIN (image->numRows, SubImageCenterRow + radiusS32 + 1);
+    psS32 SubImageStartCol  = PS_MAX (0, SubImageCenterCol - radiusS32);
+    psS32 SubImageEndCol    = PS_MIN (image->numCols, SubImageCenterCol + radiusS32 + 1);
+
+    // XXX: Must recycle image.
+    // XXX EAM: this message reflects a programming error we know about.
+    //          i am setting it to a trace message which we can take out
+    if (source->pixels != NULL) {
+        psTrace (".psModule.pmObjects.pmSourceSetPixelsCircle", 4,
+                 "WARNING: pmSourceSetPixelsCircle(): image->pixels not NULL.  Freeing and reallocating.\n");
+        psFree(source->pixels);
+    }
+    source->pixels = psImageSubset((psImage *) image, psRegionSet(SubImageStartCol,
+								  SubImageStartRow,
+								  SubImageEndCol,
+								  SubImageEndRow));
+
+    // XXX: Must recycle image.
+    if (source->mask != NULL) {
+        psFree(source->mask);
+    }
+    source->mask = psImageAlloc(source->pixels->numCols,
+                                source->pixels->numRows,
+                                PS_TYPE_U8); // XXX EAM : type was F32
+
+    //
+    // Loop through the subimage mask, initialize mask to 0 or 1.
+    // XXX EAM: valid pixels should have 0, not 1
+    for (psS32 row = 0 ; row < source->mask->numRows; row++) {
+        for (psS32 col = 0 ; col < source->mask->numCols; col++) {
+
+            if (checkRadius2((psF32) radiusS32,
+                             (psF32) radiusS32,
+                             radius,
+                             (psF32) col,
+                             (psF32) row)) {
+                source->mask->data.U8[row][col] = 0;
+            } else {
+                source->mask->data.U8[row][col] = 1;
+            }
+        }
+    }
+    return(true);
+}
+
+/******************************************************************************
+pmSourceModelGuess(source, model): This function allocates a new
+pmModel structure based on the given modelType specified in the argument list.  
+The corresponding pmModelGuess function is returned, and used to 
+supply the values of the params array in the pmModel structure.  
+ 
+XXX: Many of the initial parameters are set to 0.0 since I don't know what
+the appropiate initial guesses are.
+ 
+XXX: Many parameters are based on the src->moments structure, which is in
+image, not subImage coords.  Therefore, the calls to the model evaluation
+functions will be in image, not subImage coords.  Remember this.
+*****************************************************************************/
+pmModel *pmSourceModelGuess(pmSource *source,
+			    pmModelType modelType)
+{
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+
+    pmModel *model = pmModelAlloc(modelType);
+
+    pmModelGuessFunc modelGuessFunc = pmModelGuessFunc_GetFunction (modelType);
+    modelGuessFunc (model, source);
+    return(model);
+}
+
+/******************************************************************************
+evalModel(source, level, row): a private function which evaluates the
+source->modelPSF function at the specified coords.  The coords are subImage, not
+image coords.
+ 
+NOTE: The coords are in subImage source->pixel coords, not image coords.
+ 
+XXX: reverse order of row,col args?
+ 
+XXX: rename all coords in this file such that their name defines whether
+the coords is in subImage or image space.
+ 
+XXX: This should probably be a public pmModules function.
+ 
+XXX: Use static vectors for x.
+ 
+XXX: Figure out if it's (row, col) or (col, row) for the model functions.
+ 
+XXX: For a while, the first psVectorAlloc() was generating a seg fault during
+testing.  Try to reproduce that and debug.
+*****************************************************************************/
+
+// XXX EAM : I have made this a public function
+// XXX EAM : this now uses a pmModel as the input
+// XXX EAM : it was using src->type to find the model, not model->type
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row)
+{
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_PTR_NON_NULL(model->params, false);
+
+    // Allocate the x coordinate structure and convert row/col to image space.
+    //
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    x->data.F32[0] = (psF32) (col + image->col0);
+    x->data.F32[1] = (psF32) (row + image->row0);
+    psF32 tmpF;
+    pmModelFunc modelFunc;
+
+    modelFunc = pmModelFunc_GetFunction (model->type);
+    tmpF = modelFunc (NULL, model->params, x);
+    psFree(x);
+    return(tmpF);
+}
+
+/******************************************************************************
+findValue(source, level, row, col, dir): a private function which determines
+the column coordinate of the model function which has the value "level".  If
+dir equals 0, then you loop leftwards from the peak pixel, otherwise,
+rightwards.
+ 
+XXX: reverse order of row,col args?
+ 
+XXX: Input row/col are in image coords.
+ 
+XXX: The result is returned in image coords.
+*****************************************************************************/
+static psF32 findValue(pmSource *source,
+                       psF32 level,
+                       psU32 row,
+                       psU32 col,
+                       psU32 dir)
+{
+    //
+    // Convert coords to subImage space.
+    //
+    psU32 subRow = row - source->pixels->row0;
+    psU32 subCol = col - source->pixels->col0;
+
+    // Ensure that the starting column is allowable.
+    if (!((0 <= subCol) && (subCol < source->pixels->numCols))) {
+        psError(PS_ERR_UNKNOWN, true, "Starting column outside subImage range");
+        return(NAN);
+    }
+    if (!((0 <= subRow) && (subRow < source->pixels->numRows))) {
+        psError(PS_ERR_UNKNOWN, true, "Starting row outside subImage range");
+        return(NAN);
+    }
+
+    // XXX EAM : i changed this to match pmModelEval above, but see
+    // XXX EAM   the note below in pmSourceContour
+    psF32 oldValue = pmModelEval(source->modelFLT, source->pixels, subCol, subRow);
+    if (oldValue == level) {
+        return(((psF32) (subCol + source->pixels->col0)));
+    }
+
+    //
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+    //
+    psS32 incr;
+    psS32 lastColumn;
+    if (dir == 0) {
+        incr = -1;
+        lastColumn = -1;
+    } else {
+        incr = 1;
+        lastColumn = source->pixels->numCols;
+    }
+    subCol+=incr;
+
+    while (subCol != lastColumn) {
+        psF32 newValue = pmModelEval(source->modelFLT, source->pixels, subCol, subRow);
+        if (oldValue == level) {
+            return((psF32) (subCol + source->pixels->col0));
+        }
+
+        if ((newValue <= level) && (level <= oldValue)) {
+            // This is simple linear interpolation.
+            return( ((psF32) (subCol + source->pixels->col0)) + ((psF32) incr) * ((level - newValue) / (oldValue - newValue)) );
+        }
+
+        if ((oldValue <= level) && (level <= newValue)) {
+            // This is simple linear interpolation.
+            return( ((psF32) (subCol + source->pixels->col0)) + ((psF32) incr) * ((level - oldValue) / (newValue - oldValue)) );
+        }
+
+        subCol+=incr;
+    }
+
+    return(NAN);
+}
+
+/******************************************************************************
+pmSourceContour(src, img, level, mode): For an input subImage, and model, this
+routine returns a psArray of coordinates that evaluate to the specified level.
+ 
+XXX: Probably should remove the "image" argument.
+XXX: What type should the output coordinate vectors consist of?  col,row?
+XXX: Why a pmArray output?
+XXX: doex x,y correspond with col,row or row/col?
+XXX: What is mode?
+XXX: The top, bottom of the contour is not correctly determined.
+XXX EAM : this functions is using the model for the contour, but it should
+          be using only the image counts
+*****************************************************************************/
+psArray *pmSourceContour(pmSource *source,
+                         const psImage *image,
+                         psF32 level,
+                         pmContourType mode)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->modelFLT, false);
+    // XXX EAM : what is the purpose of modelPSF/modelFLT?
+
+    //
+    // Allocate data for x/y pairs.
+    //
+    psVector *xVec = psVectorAlloc(2 * source->pixels->numRows, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc(2 * source->pixels->numRows, PS_TYPE_F32);
+
+    //
+    // Start at the row with peak pixel, then decrement.
+    //
+    psS32 col = source->peak->x;
+    for (psS32 row = source->peak->y; row>= 0 ; row--) {
+        // XXX: yVec contain no real information.  Do we really need it?
+        yVec->data.F32[row] = (psF32) (source->pixels->row0 + row);
+        yVec->data.F32[row+yVec->n] = (psF32) (source->pixels->row0 + row);
+
+        // Starting at peak pixel, search leftwards for the column intercept.
+        psF32 leftIntercept = findValue(source, level, row, col, 0);
+        if (isnan(leftIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row] = ((psF32) source->pixels->col0) + leftIntercept;
+
+        // Starting at peak pixel, search rightwards for the column intercept.
+
+        psF32 rightIntercept = findValue(source, level, row, col, 1);
+        if (isnan(rightIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        //printf("HERE the intercepts are (%.2f, %.2f)\n", leftIntercept, rightIntercept);
+        xVec->data.F32[row+xVec->n] = ((psF32) source->pixels->col0) + rightIntercept;
+
+        // Set starting column for next row
+        col = (psS32) ((leftIntercept + rightIntercept) / 2.0);
+    }
+    //
+    // Start at the row (+1) with peak pixel, then increment.
+    //
+    col = source->peak->x;
+    for (psS32 row = 1 + source->peak->y; row < source->pixels->numRows ; row++) {
+        // XXX: yVec contain no real information.  Do we really need it?
+        yVec->data.F32[row] = (psF32) (source->pixels->row0 + row);
+        yVec->data.F32[row+yVec->n] = (psF32) (source->pixels->row0 + row);
+
+        // Starting at peak pixel, search leftwards for the column intercept.
+        psF32 leftIntercept = findValue(source, level, row, col, 0);
+        if (isnan(leftIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row] = ((psF32) source->pixels->col0) + leftIntercept;
+
+        // Starting at peak pixel, search rightwards for the column intercept.
+        psF32 rightIntercept = findValue(source, level, row, col, 1);
+        if (isnan(rightIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row+xVec->n] = ((psF32) source->pixels->col0) + rightIntercept;
+
+        // Set starting column for next row
+        col = (psS32) ((leftIntercept + rightIntercept) / 2.0);
+    }
+
+    //
+    // Allocate an array for result, store coord vectors there.
+    //
+    psArray *tmpArray = psArrayAlloc(2);
+    tmpArray->data[0] = (psPtr *) yVec;
+    tmpArray->data[1] = (psPtr *) xVec;
+    return(tmpArray);
+}
+
+// XXX EAM : these are better starting values, but should be available from metadata?
+#define PM_SOURCE_FIT_MODEL_NUM_ITERATIONS 15
+#define PM_SOURCE_FIT_MODEL_TOLERANCE 0.1
+/******************************************************************************
+pmSourceFitModel(source, model): must create the appropiate arguments to the
+LM minimization routines for the various p_pmMinLM_XXXXXX_Vec() functions.
+ 
+XXX: should there be a mask value?
+XXX EAM : fit the specified model (not necessarily the one in source)
+*****************************************************************************/
+bool pmSourceFitModel_v5(pmSource *source,
+			 pmModel *model,
+			 const bool PSF)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+    psBool fitStatus = true;
+    psBool onPic     = true;
+    psBool rc        = true;
+
+    // XXX EAM : is it necessary for the mask & weight to exist?  the
+    //           tests below could be conditions (!NULL)
+
+    psVector *params = model->params;
+    psVector *dparams = model->dparams;
+    psVector *paramMask = NULL;
+
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+
+    int nParams = PSF ? params->n - 4 : params->n;
+
+    // find the number of valid pixels
+    // XXX EAM : this loop and the loop below could just be one pass
+    //           using the psArrayAdd and psVectorExtend functions
+    psS32 count = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            if (source->mask->data.U8[i][j] == 0) {
+                count++;
+            }
+        }
+    }
+    if (count <  nParams + 1) {
+	psTrace (".pmObjects.pmSourceFitModel", 4, "insufficient valid pixels\n");
+	return(false);
+    }
+
+    // construct the coordinate and value entries
+    psArray *x = psArrayAlloc(count);
+    psVector *y = psVectorAlloc(count, PS_TYPE_F32);
+    psVector *yErr = psVectorAlloc(count, PS_TYPE_F32);
+    psS32 tmpCnt = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            if (source->mask->data.U8[i][j] == 0) {
+                psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+                // XXX: Convert i/j to image space:
+                // XXX EAM: coord order is (x,y) == (col,row)
+                coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+                coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+                x->data[tmpCnt] = (psPtr *) coord;
+                y->data.F32[tmpCnt] = source->pixels->data.F32[i][j];
+                yErr->data.F32[tmpCnt] = sqrt (source->weight->data.F32[i][j]);
+		// XXX EAM : note the wasted effort: we carry dY^2, take sqrt(), then 
+		//           the minimization function calculates sq()
+                tmpCnt++;
+            }
+        }
+    }
+
+    psMinimization *myMin = psMinimizationAlloc(PM_SOURCE_FIT_MODEL_NUM_ITERATIONS,
+						PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    // PSF model only fits first 4 parameters, FLT model fits all
+    if (PSF) {
+	paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
+	for (int i = 0; i < 4; i++) {
+	    paramMask->data.U8[i] = 0;
+	}
+	for (int i = 4; i < paramMask->n; i++) {
+	    paramMask->data.U8[i] = 1;
+	}
+    }       
+
+    // XXX EAM : covar must be F64?
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+
+    psTrace (".pmObjects.pmSourceFitModel", 5, "fitting function\n");
+    fitStatus = psMinimizeLMChi2(myMin, covar, params, paramMask, x, y, yErr, modelFunc);
+    for (int i = 0; i < dparams->n; i++) {
+	if ((paramMask != NULL) && paramMask->data.U8[i]) continue;
+	dparams->data.F32[i] = sqrt(covar->data.F64[i][i]);
+    }
+ 
+    // XXX EAM: we need to do something (give an error?) if rc is false
+    // XXX EAM: psMinimizeLMChi2 does not check convergence
+
+    // XXX models can go insane: reject these
+    onPic &= (params->data.F32[2] >= source->pixels->col0);
+    onPic &= (params->data.F32[2] <  source->pixels->col0 + source->pixels->numCols);
+    onPic &= (params->data.F32[3] >= source->pixels->row0);
+    onPic &= (params->data.F32[3] <  source->pixels->row0 + source->pixels->numRows);
+
+    // XXX EAM: save the resulting chisq, nDOF, nIter
+    model->chisq = myMin->value;
+    model->nIter = myMin->iter;
+    model->nDOF  = y->n - nParams;
+
+    // XXX EAM get the Gauss-Newton distance for fixed model parameters
+    if (paramMask != NULL) {
+	psVector *delta = psVectorAlloc (params->n, PS_TYPE_F64);
+	psMinimizeGaussNewtonDelta (delta, params, NULL, x, y, yErr, modelFunc);
+	for (int i = 0; i < dparams->n; i++) {
+	    if (!paramMask->data.U8[i]) continue;
+	    dparams->data.F32[i] = delta->data.F64[i];
+	}
+	psFree (delta);
+    }
+
+    psFree(x);
+    psFree(y);
+    psFree(yErr);
+    psFree(myMin);
+    psFree(covar);
+    psFree(paramMask);
+
+    rc = (onPic && fitStatus);
+    return(rc);
+}
+
+// XXX EAM : new version with parameter range limits and weight enhancement
+bool pmSourceFitModel (pmSource *source,
+		       pmModel *model,
+		       const bool PSF)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    // XXX EAM : is it necessary for the mask & weight to exist?  the
+    //           tests below could be conditions (!NULL)
+
+    psBool fitStatus = true;
+    psBool onPic     = true;
+    psBool rc        = true;
+    psF32  Ro, ymodel;
+
+    psVector *params = model->params;
+    psVector *dparams = model->dparams;
+    psVector *paramMask = NULL;
+
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+
+    // XXX EAM : I need to use the sky value to constrain the weight model
+    int nParams = PSF ? params->n - 4 : params->n;
+    psF32 So = params->data.F32[0];
+
+    // find the number of valid pixels
+    // XXX EAM : this loop and the loop below could just be one pass
+    //           using the psArrayAdd and psVectorExtend functions
+    psS32 count = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            if (source->mask->data.U8[i][j] == 0) {
+                count++;
+            }
+        }
+    }
+    if (count <  nParams + 1) {
+	psTrace (".pmObjects.pmSourceFitModel", 4, "insufficient valid pixels\n");
+	return(false);
+    }
+
+    // construct the coordinate and value entries
+    psArray *x = psArrayAlloc(count);
+    psVector *y = psVectorAlloc(count, PS_TYPE_F32);
+    psVector *yErr = psVectorAlloc(count, PS_TYPE_F32);
+    psS32 tmpCnt = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            if (source->mask->data.U8[i][j] == 0) {
+                psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+                // XXX: Convert i/j to image space:
+                // XXX EAM: coord order is (x,y) == (col,row)
+                coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+                coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+                x->data[tmpCnt] = (psPtr *) coord;
+                y->data.F32[tmpCnt] = source->pixels->data.F32[i][j];
+
+		// compare observed flux to model flux to adjust weight
+		ymodel = modelFunc (NULL, model->params, coord);
+		
+		// this test modifies the weight based on deviation from the model flux
+		Ro = 1.0 + fabs (y->data.F32[tmpCnt] - ymodel) / sqrt(PS_SQR(ymodel - So) + PS_SQR(So));
+
+		// psMinimizeLMChi2_EAM takes wt = 1/dY^2
+		if (source->weight->data.F32[i][j] == 0) {
+		  yErr->data.F32[tmpCnt] = 0.0;
+		} else {
+		  yErr->data.F32[tmpCnt] = 1.0 / (source->weight->data.F32[i][j] * Ro);
+		}
+                tmpCnt++;
+            }
+        }
+    }
+
+    psMinimization *myMin = psMinimizationAlloc(PM_SOURCE_FIT_MODEL_NUM_ITERATIONS,
+						PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    // PSF model only fits first 4 parameters, FLT model fits all
+    if (PSF) {
+	paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
+	for (int i = 0; i < 4; i++) {
+	    paramMask->data.U8[i] = 0;
+	}
+	for (int i = 4; i < paramMask->n; i++) {
+	    paramMask->data.U8[i] = 1;
+	}
+    } 
+    # if (0)
+    else {
+	// try keeping the moments sky -- unsuccessful
+	for (int i = 1; i < paramMask->n; i++) {
+	    paramMask->data.U8[i] = 0;
+	}
+	paramMask->data.U8[0] = 1;
+    }
+    # endif
+
+
+    // XXX EAM : I've added three types of parameter range checks
+    // XXX EAM : this requires my new psMinimization functions
+    pmModelLimits modelLimits = pmModelLimits_GetFunction (model->type);
+    psVector *beta_lim = NULL;
+    psVector *params_min = NULL;
+    psVector *params_max = NULL;
+
+    // XXX EAM : in this implementation, I pass in the limits with the covar matrix.
+    //           in the SDRS, I define a new psMinimization which will take these in
+    psImage *covar = psImageAlloc (params->n, 3, PS_TYPE_F64);
+    modelLimits (&beta_lim, &params_min, &params_max);
+    for (int i = 0; i < params->n; i++) {
+	covar->data.F64[0][i] = beta_lim->data.F32[i];
+	covar->data.F64[1][i] = params_min->data.F32[i];
+	covar->data.F64[2][i] = params_max->data.F32[i];
+    }
+
+    psTrace (".pmObjects.pmSourceFitModel", 5, "fitting function\n");
+    fitStatus = psMinimizeLMChi2_EAM(myMin, covar, params, paramMask, x, y, yErr, modelFunc);
+    for (int i = 0; i < dparams->n; i++) {
+	if ((paramMask != NULL) && paramMask->data.U8[i]) continue;
+	dparams->data.F32[i] = sqrt(covar->data.F64[i][i]);
+    }
+ 
+    // XXX EAM: we need to do something (give an error?) if rc is false
+    // XXX EAM: psMinimizeLMChi2 does not check convergence
+
+    // XXX models can go insane: reject these
+    onPic &= (params->data.F32[2] >= source->pixels->col0);
+    onPic &= (params->data.F32[2] <  source->pixels->col0 + source->pixels->numCols);
+    onPic &= (params->data.F32[3] >= source->pixels->row0);
+    onPic &= (params->data.F32[3] <  source->pixels->row0 + source->pixels->numRows);
+
+    // XXX EAM: save the resulting chisq, nDOF, nIter
+    model->chisq = myMin->value;
+    model->nIter = myMin->iter;
+    model->nDOF  = y->n - nParams;
+
+    // XXX EAM get the Gauss-Newton distance for fixed model parameters
+    if (paramMask != NULL) {
+	psVector *delta = psVectorAlloc (params->n, PS_TYPE_F64);
+	psMinimizeGaussNewtonDelta_EAM (delta, params, NULL, x, y, yErr, modelFunc);
+	for (int i = 0; i < dparams->n; i++) {
+	    if (!paramMask->data.U8[i]) continue;
+	    dparams->data.F32[i] = delta->data.F64[i];
+	}
+    }
+
+    psFree(paramMask);
+    psFree(x);
+    psFree(y);
+    psFree(myMin);
+
+    rc = (onPic && fitStatus);
+    return(rc);
+}
+
+bool p_pmSourceAddOrSubModel(psImage *image,
+			     psImage *mask,
+                             pmModel *model,
+                             bool center,
+                             psS32 flag)
+{
+  
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *params = model->params;
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+    psS32 imageCol;
+    psS32 imageRow;
+
+    for (psS32 i = 0; i < image->numRows; i++) {
+        for (psS32 j = 0; j < image->numCols; j++) {
+	    if ((mask != NULL) && mask->data.U8[i][j]) continue;
+            psF32 pixelValue;
+            // XXX: Should you be adding the pixels for the entire subImage,
+            // or a radius of pixels around it?
+
+            // Convert i/j to imace coord space:
+            // XXX: Make sure you have col/row order correct.
+	    // XXX EAM : 'center' option changes this
+	    // XXX EAM : i == numCols/2 -> x = model->params->data.F32[2]
+	    if (center) {
+		imageCol = j - 0.5*image->numCols + model->params->data.F32[2];
+		imageRow = i - 0.5*image->numRows + model->params->data.F32[3];
+	    } else {
+		imageCol = j + image->col0;
+		imageRow = i + image->row0;
+	    }
+
+            x->data.F32[0] = (float) imageCol;
+            x->data.F32[1] = (float) imageRow;
+	    pixelValue = modelFunc (NULL, params, x);
+	    // fprintf (stderr, "%f %f  %d %d  %f\n", x->data.F32[0], x->data.F32[1], i, j, pixelValue);
+
+            if (flag == 1) {
+                pixelValue = -pixelValue;
+            }
+
+            // XXX: Must figure out how to calculate the image coordinates and
+            // how to use the boolean "center" flag.
+
+            image->data.F32[i][j]+= pixelValue;
+        }
+    }
+    psFree(x);
+    return(true);
+}
+
+
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmSourceAddModel(psImage *image,
+		      psImage *mask,
+                      pmModel *model,
+                      bool center)
+{
+    return(p_pmSourceAddOrSubModel(image, mask, model, center, 0));
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmSourceSubModel(psImage *image,
+		      psImage *mask,
+                      pmModel *model,
+                      bool center)
+{
+    return(p_pmSourceAddOrSubModel(image, mask, model, center, 1));
+}
+
+
+// XXX: Put this is psConstants.h
+#define PS_VECTOR_CHECK_SIZE(VEC1, N, RVAL) \
+if (VEC1->n != N) { \
+    psError(PS_ERR_BAD_PARAMETER_SIZE, true, \
+            "psVector %s has size %d, should be %d.", \
+            #VEC1, VEC1->n, N); \
+    return(RVAL); \
+}
Index: /tags/psphot_dev_02/psphot/src/pmObjects_EAM.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmObjects_EAM.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmObjects_EAM.h	(revision 5352)
@@ -0,0 +1,312 @@
+/** @file  pmObjects.h
+ *
+ *  This file will ...
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-09-21 06:52:23 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_OBJECTS_H)
+#define PM_OBJECTS_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include<stdio.h>
+#include<math.h>
+#include "pslib.h"
+#include "psLibUtils.h"
+// #include "pmModelGroup.h"
+
+// defined mask values for mask image pixels.
+// this is probably a bad solution: we will want to set mask values
+// outside of the PSPHOT code.  Perhaps we can set up a registered 
+// set of mask values with specific meanings that other functions
+// can add to or define?
+enum {
+    PSPHOT_MASK_CLEAR     = 0x00,
+    PSPHOT_MASK_INVALID   = 0x01,
+    PSPHOT_MASK_SATURATED = 0x02,
+    PSPHOT_MASK_MARKED    = 0x08,
+} psphotMaskValues;
+
+/** pmPeakType
+ * 
+ *  A peak pixel may have several features which may be determined when the
+ *  peak is found or measured. These are specified by the pmPeakType enum.
+ *  PM_PEAK_LONE represents a single pixel which is higher than its 8 immediate
+ *  neighbors.  The PM_PEAK_EDGE represents a peak pixel which touching the image
+ *  edge. The PM_PEAK_FLAT represents a peak pixel which has more than a specific
+ *  number of neighbors at the same value, within some tolarence:
+ * 
+ */
+typedef enum {
+    PM_PEAK_LONE,                       ///< Isolated peak.
+    PM_PEAK_EDGE,                       ///< Peak on edge.
+    PM_PEAK_FLAT,                       ///< Peak has equal-value neighbors.
+    PM_PEAK_UNDEF                       ///< Undefined.
+} pmPeakType;
+
+/** pmPeak data structure
+ *  
+ *  
+ *  
+ */
+typedef struct
+{
+    int x;                              ///< X-coordinate of peak pixel.
+    int y;                              ///< Y-coordinate of peak pixel.
+    float counts;                       ///< Value of peak pixel (above sky?).
+    pmPeakType class;                   ///< Description of peak.
+}
+pmPeak;
+
+/** pmMoments data structure
+ *  
+ *  
+ *  
+ */
+typedef struct
+{
+    float x;				///< X-coord of centroid.
+    float y;				///< Y-coord of centroid.
+    float Sx;				///< x-second moment.
+    float Sy;				///< y-second moment.
+    float Sxy;				///< xy cross moment.
+    float Sum;				///< Pixel sum above sky (background).
+    float Peak;				///< Peak counts above sky.
+    float Sky;				///< Sky level (background).
+    float SN;				///< approx signal-to-noise
+    int nPixels;			///< Number of pixels used.
+} pmMoments;
+
+/** pmPSFClump data structure
+ *  
+ *  
+ *  
+ */
+typedef struct
+{
+    float X;
+    float dX;
+    float Y;
+    float dY;
+} pmPSFClump;
+
+typedef int pmModelType;
+
+/** pmModel data structure
+ *  
+ *  
+ *  
+ */
+typedef struct
+{
+    pmModelType type;			///< Model to be used.
+    psVector *params;			///< Paramater values.
+    psVector *dparams;			///< Parameter errors.
+    float chisq;			///< Fit chi-squared.
+    int nDOF;				///< number of degrees of freedom
+    int nIter;				///< number of iterations to reach min
+    float radius;			///< fit radius actually used
+}
+pmModel;
+
+/** pmSourceType enumeration
+ *  
+ *  
+ *  
+ */
+typedef enum {
+    PM_SOURCE_DEFECT,
+    PM_SOURCE_SATURATED,
+
+    PM_SOURCE_SATSTAR,
+    PM_SOURCE_PSFSTAR,
+    PM_SOURCE_GOODSTAR,
+
+    PM_SOURCE_POOR_FIT_PSF,
+    PM_SOURCE_FAIL_FIT_PSF,
+    PM_SOURCE_FAINTSTAR,
+
+    PM_SOURCE_GALAXY,
+    PM_SOURCE_FAINT_GALAXY,
+    PM_SOURCE_DROP_GALAXY,
+    PM_SOURCE_FAIL_FIT_GAL,
+    PM_SOURCE_POOR_FIT_GAL,
+
+    PM_SOURCE_OTHER,
+} pmSourceType;
+
+/** pmSource data structure
+ *  
+ *  This source has the capacity for several types of measurements. The
+ *  simplest measurement of a source is the location and flux of the peak pixel
+ *  associated with the source:
+ *  
+ */
+typedef struct
+{
+    pmPeak *peak;			///< Description of peak pixel.
+    psImage *pixels;			///< Rectangular region including object pixels.
+    psImage *weight;			///< Image variance.
+    psImage *mask;			///< Mask which marks pixels associated with objects.
+    pmMoments *moments;			///< Basic moments measure for the object.
+    pmModel *modelPSF;			///< PSF Model fit (parameters and type)
+    pmModel *modelFLT;			///< FLT Model fit (parameters and type).
+    pmSourceType type;			///< Best identification of object.
+    float apMag;
+    float fitMag;
+}
+pmSource;
+
+pmPeak *pmPeakAlloc(
+    int x,				///< Row-coordinate in image space
+    int y,				///< Col-coordinate in image space
+    float counts,			///< The value of the peak pixel
+    pmPeakType class			///< The type of peak pixel
+);
+
+pmMoments *pmMomentsAlloc();
+pmModel *pmModelAlloc(pmModelType type);
+pmSource  *pmSourceAlloc();
+
+/******************************************************************************
+pmFindVectorPeaks(vector, threshold): Find all local peaks in the given vector
+above the given threshold.  Returns a vector of type PS_TYPE_U32 containing
+the location (x value) of all peaks.
+*****************************************************************************/
+psVector *pmFindVectorPeaks(
+    const psVector *vector,		///< The input vector (float)
+    float threshold			///< Threshold above which to find a peak
+);
+
+/******************************************************************************
+pmFindImagePeaks(image, threshold): Find all local peaks in the given psImage
+above the given threshold.  Returns a psList containing the location (x/y
+value) of all peaks.
+*****************************************************************************/
+psArray *pmFindImagePeaks(
+    const psImage *image,		///< The input image where peaks will be found (float)
+    float threshold			///< Threshold above which to find a peak
+);
+
+/******************************************************************************
+pmCullPeaks(peaks, maxValue, valid): eliminate peaks from the psList that have
+a peak value above the given maximum, or fall outside the valid region.
+*****************************************************************************/
+psList *pmCullPeaks(
+    psList *peaks,			///< The psList of peaks to be culled
+    float maxValue,			///< Cull peaks above this value
+    const psRegion *valid		///< Cull peaks otside this psRegion
+);
+
+/******************************************************************************
+pmSource *pmSourceLocalSky(image, peak, innerRadius, outerRadius):
+
+*****************************************************************************/
+bool pmSourceLocalSky(
+    pmSource *source,			///< The input image (float)
+    psStatsOptions statsOptions,	///< The statistic used in calculating the background sky
+    float Radius			///< The inner radius of the square annulus to exclude
+    );
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmSourceMoments(
+    pmSource *source,			///< The input pmSource for which moments will be computed
+    float radius			///< Use a circle of pixels around the peak
+);
+
+/******************************************************************************
+pmSourcePSFClump(pmArray *source, psMetaDeta *metadata): find the source PSF clump
+*****************************************************************************/
+pmPSFClump pmSourcePSFClump(
+    psArray *source,			///< The input pmSource
+    psMetadata *metadata		///< Contains classification parameters
+);
+
+/******************************************************************************
+pmSourceRoughClass(pmArray *source, psMetaDeta *metadata): make a guess at the
+source classification.
+*****************************************************************************/
+bool pmSourceRoughClass(
+    psArray *source,			///< The input pmSource
+    psMetadata *metadata,		///< Contains classification parameters
+    pmPSFClump clump			///< Statistics about the PSF clump
+);
+
+/******************************************************************************
+pmSourceSetPixelCircle(source, image, radius)
+*****************************************************************************/
+bool pmSourceSetPixelsCircle(
+    pmSource *source,			///< The input pmSource
+    const psImage *image,		///< The input image (float)
+    float radius			///< The radius of the circle
+);
+
+/******************************************************************************
+ *****************************************************************************/
+pmModel *pmSourceModelGuess(
+    pmSource *source,			///< The input pmSource
+    pmModelType model			///< The type of model to be created.
+);
+
+/******************************************************************************
+ *****************************************************************************/
+typedef enum {
+    PS_CONTOUR_CRUDE,
+} pmContourType;
+
+psArray *pmSourceContour(
+    pmSource *source,			///< The input pmSource
+    const psImage *image,		///< The input image (float) (this arg should be removed)
+    float level,			///< The level of the contour
+    pmContourType mode			///< Currently this must be PS_CONTOUR_CRUDE
+);
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmSourceFitModel(
+    pmSource *source,			///< The input pmSource
+    pmModel *model,			///< model to be fitted
+    const bool PSF			///< Treat model as PSF or FLT?
+);
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmSourceAddModel(
+    psImage *image,			///< The output image (float)
+    psImage *mask,			///< The image pixel mask (valid == 0)
+    pmModel *model,			///< The input pmModel
+    bool center				///< A boolean flag that determines whether pixels are centered
+);
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmSourceSubModel(
+    psImage *image,			///< The output image (float)
+    psImage *mask,			///< The image pixel mask (valid == 0)
+    pmModel *model,			///< The input pmModel
+    bool center				///< A boolean flag that determines whether pixels are centered
+);
+
+bool pmSourceFitModel_v5(
+    pmSource *source,			///< The input pmSource
+    pmModel *model,			///< model to be fitted
+    const bool PSF			///< Treat model as PSF or FLT?
+);
+
+bool pmSourceFitModel_v7(
+    pmSource *source,			///< The input pmSource
+    pmModel *model,			///< model to be fitted
+    const bool PSF			///< Treat model as PSF or FLT?
+);
+
+#endif
Index: /tags/psphot_dev_02/psphot/src/pmPSF.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmPSF.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmPSF.c	(revision 5352)
@@ -0,0 +1,128 @@
+# include <pslib.h>
+# include "psLibUtils.h"
+# include "pmObjects_EAM.h"
+# include "pmPSF.h"
+# include "pmModelGroup.h"
+
+/********  pmPSF functions  **************************************************/
+
+// function to free a pmPSF structure
+static void pmPSFFree (pmPSF *psf) {
+
+  if (psf == NULL) return;
+
+  psFree (psf->params);
+  return;
+}
+
+// allocate a pmPSF
+// a PSF always has 4 parameters fewer than the equivalent model
+pmPSF *pmPSFAlloc (pmModelType type) {
+
+    int Nparams;
+
+    pmPSF *psf = (pmPSF *) psAlloc(sizeof(pmPSF));
+
+    psf->type     = type;
+    psf->chisq    = 0.0;
+    psf->ApResid  = 0.0;
+    psf->dApResid = 0.0;
+    psf->skyBias  = 0.0;
+
+    Nparams = pmModelParameterCount (type);
+    if (!Nparams) {
+	psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+	return(NULL);
+    }      
+
+    psf->params = psArrayAlloc (Nparams - 4);
+    for (int i = 0; i < psf->params->n; i++) {
+	// XXX EAM : make this a user-defined value?
+	// XXX EAM : future version (0.7.0?) psf->params->data[i] = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 1, 1);
+	if (1) {
+	    psf->params->data[i] = Polynomial2DAlloc_EAM(PS_POLYNOMIAL_ORD, 1, 1);
+	} else {
+	    psf->params->data[i] = Polynomial2DAlloc_EAM(PS_POLYNOMIAL_ORD, 0, 0);
+	}
+    }
+
+    psMemSetDeallocator(psf, (psFreeFunc) pmPSFFree);
+    return(psf);
+}
+
+// build a PSF from a collection of free-fitted models
+//   the PSF ignores the first 4 (independent) model parameters
+//   and constructs a polynomial fit to the remaining as a function
+//   of image coordinate
+// input: an array of pmModels, pre-allocated psf
+// some of the array entries may be NULL (failed fits), ignore them
+bool pmPSFFromModels (pmPSF *psf, psArray *models, psVector *mask) {
+
+    // construct the fit vectors from the collection of objects
+    // use the mask to ignore missing fits 
+    psVector *x  = psVectorAlloc (models->n, PS_TYPE_F64);
+    psVector *y  = psVectorAlloc (models->n, PS_TYPE_F64);
+    psVector *z  = psVectorAlloc (models->n, PS_TYPE_F64);
+    psVector *dz = psVectorAlloc (models->n, PS_TYPE_F64);
+
+    for (int i = 0; i < models->n; i++) {
+	pmModel *model = models->data[i];
+	if (model == NULL) continue;
+
+	// XXX EAM : this is fragile: x and y must be stored consistently at 2,3
+	// note that the data is provided in the F64 array
+	x->data.F64[i] = model->params->data.F32[2];
+	y->data.F64[i] = model->params->data.F32[3];
+    }
+
+    // we are doing a robust fit.  after each pass, we drop points which are 
+    // more deviant than three sigma. 
+    // mask is currently updated for each pass. this is inconsistent?
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+
+    for (int i = 0; i < psf->params->n; i++) {
+	for (int j = 0; j < models->n; j++) {
+	    pmModel *model = models->data[j];
+	    if (model == NULL) continue;
+	    z->data.F64[j] = model->params->data.F32[i + 4];
+	    dz->data.F64[j] = 1;
+	    // XXX EAM : need to use actual errors?
+	    // XXX EAM : this is fragile: psf(Nparams) = model(Nparams) - 4
+	}
+
+	// XXX EAM : this is the expected API (cycle 7? cycle 8?)
+	psf->params->data[i] = VectorClipFitPolynomial2D_EAM (psf->params->data[i], stats, mask, 0xff, z, dz, x, y);
+
+	// XXX EAM : drop this when above is implemented...
+	// psf->params->data[i] = RobustFit2D (psf->params->data[i], mask, x, y, z, dz);
+
+	// psTrace ("psphot.psftest", 3, "keeping %d of %d PSF candidates (PSF param %d)\n", Nkeep, mask->n, i);
+	// psPolynomial2DDump (psf->params->data[i]);
+    }
+    
+    psFree (stats);
+    psFree (x);
+    psFree (y);
+    psFree (z);
+    psFree (dz);
+    return (true);
+}
+
+// use the model position parameters to construct a realization of the PSF model
+// at the object coordinates
+pmModel *pmModelFromPSF (pmModel *modelFLT, pmPSF *psf) {
+
+    // need to define the relationship between the modelFLT and modelPSF ?
+    
+    // find function used to set the model parameters
+    pmModelFromPSFFunc modelFromPSFFunc = pmModelFromPSFFunc_GetFunction (psf->type);
+
+    // allocate a new pmModel to hold the PSF version 
+    pmModel *modelPSF = pmModelAlloc (psf->type);
+
+    // set model parameters for this source based on PSF information
+    modelFromPSFFunc (modelPSF, modelFLT, psf);
+
+    return (modelPSF);
+}
Index: /tags/psphot_dev_02/psphot/src/pmPSF.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmPSF.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmPSF.h	(revision 5352)
@@ -0,0 +1,27 @@
+
+# ifndef PM_PSF_H 
+# define PM_PSF_H
+
+/** pmPSF data structure
+ *  
+ *  
+ *  
+ */
+typedef struct
+{
+    pmModelType type;			///< PSF Model in use
+    psArray *params;			///< Model parameters (psPolynomial2D)
+    float chisq;			///< PSF goodness statistic
+    float ApResid;
+    float dApResid;
+    float skyBias;
+    int nPSFstars;			///< number of stars used to measure PSF
+}
+pmPSF;
+
+// pmPSF utilities
+pmPSF       *pmPSFAlloc (pmModelType type);
+bool	     pmPSFFromModels (pmPSF *psf, psArray *models, psVector *mask);
+pmModel	    *pmModelFromPSF (pmModel *model, pmPSF *psf);
+
+# endif
Index: /tags/psphot_dev_02/psphot/src/pmPSFtry.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmPSFtry.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmPSFtry.c	(revision 5352)
@@ -0,0 +1,367 @@
+# include <pslib.h>
+# include "psLibUtils.h"
+# include "pmObjects_EAM.h"
+# include "psModulesUtils.h"
+# include "pmPSF.h"
+# include "pmPSFtry.h"
+# include "pmModelGroup.h"
+
+// ********  pmPSFtry functions  **************************************************
+// * pmPSFtry holds a single pmPSF model test, with the input sources, the freely
+// * fitted version of the model, the pmPSF fit to the fitted model parameters, 
+// * and the PSF fits to the source. It also includes the statistics from the 
+// * fits, both the individual sources, and the collection
+
+// free a pmPSFtry structure
+static void pmPSFtryFree (pmPSFtry *test) {
+
+  if (test == NULL) return;
+
+  psFree (test->psf);
+  psFree (test->sources);
+  psFree (test->modelFLT);
+  psFree (test->modelPSF);
+  psFree (test->metric);
+  psFree (test->fitMag);
+  psFree (test->mask);
+  return;
+}
+
+// allocate a pmPSFtry based on the desired sources and the model (identified by name)
+pmPSFtry *pmPSFtryAlloc (psArray *sources, char *modelName) {
+
+    pmModelType type;
+
+    pmPSFtry *test = (pmPSFtry *) psAlloc(sizeof(pmPSFtry));
+
+    // XXX probably need to increment ref counter
+    type           = pmModelSetType (modelName);
+    test->psf      = pmPSFAlloc (type);
+    test->sources  = psMemCopy(sources);
+    test->modelFLT = psArrayAlloc (sources->n);
+    test->modelPSF = psArrayAlloc (sources->n);
+    test->metric   = psVectorAlloc (sources->n, PS_TYPE_F64);
+    test->fitMag   = psVectorAlloc (sources->n, PS_TYPE_F64);
+    test->mask     = psVectorAlloc (sources->n, PS_TYPE_U8);
+
+    for (int i = 0; i < test->modelFLT->n; i++) {
+	test->mask->data.U8[i]  = 0;
+	test->modelFLT->data[i] = NULL;
+	test->modelPSF->data[i] = NULL;
+	test->metric->data.F64[i] = 0;
+	test->fitMag->data.F64[i] = 0;
+    }	
+
+    psMemSetDeallocator(test, (psFreeFunc) pmPSFtryFree);
+    return (test);
+}
+
+// build a pmPSFtry for the given model:
+// - fit each source with the free-floating model
+// - construct the pmPSF from the collection of models
+// - fit each source with the PSF-parameter models 
+// - measure the pmPSF quality metric (dApResid)
+
+// sources used in for pmPSFtry may be masked by the analysis
+// mask values indicate the reason the source was rejected:
+
+pmPSFtry *pmPSFtryModel (psArray *sources, char *modelName, float RADIUS) 
+{
+    bool status;
+    float obsMag;
+    float fitMag;
+    float x;
+    float y;
+    int Nflt = 0;
+    int Npsf = 0;
+
+    pmPSFtry *try = pmPSFtryAlloc (sources, modelName);
+
+    // stage 1:  fit an independent model (freeModel) to all sources
+    psTimerStart ("fit");
+    for (int i = 0; i < try->sources->n; i++) {
+
+	pmSource *source = try->sources->data[i];
+	pmModel  *model  = pmSourceModelGuess (source, try->psf->type); 
+	x = source->peak->x;
+	y = source->peak->y;
+
+	// set temporary object mask and fit object
+	// fit model as FLT, not PSF
+	psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PSPHOT_MASK_MARKED);
+	status = pmSourceFitModel (source, model, false);
+	psImageKeepCircle (source->mask, x, y, RADIUS, "AND", ~PSPHOT_MASK_MARKED);
+
+	// exclude the poor fits
+	if (!status) {
+	  try->mask->data.U8[i] = PSFTRY_MASK_FLT_FAIL;
+	  psFree (model);
+	  continue;
+	}
+	try->modelFLT->data[i] = model;
+	Nflt ++;
+    }
+    psLogMsg ("psphot.psftry", 4, "fit flt:   %f sec for %d sources\n", psTimerMark ("fit"), sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %d PSF candidates (FLT)\n", Nflt, sources->n);
+
+    // make this optional? 
+    // DumpModelFits (try->modelFLT, "modelsFLT.dat");
+
+    // stage 2: construct a psf (pmPSF) from this collection of model fits
+    pmPSFFromModels (try->psf, try->modelFLT, try->mask);
+
+    // stage 3: refit with fixed shape parameters
+    psTimerStart ("fit");
+    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];
+	pmModel  *modelFLT = try->modelFLT->data[i];
+
+	// set shape for this model based on PSF
+	pmModel *modelPSF = pmModelFromPSF (modelFLT, try->psf); 
+	x = source->peak->x;
+	y = source->peak->y;
+
+	psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PSPHOT_MASK_MARKED);
+	status = pmSourceFitModel (source, modelPSF, true);
+
+	// skip poor fits
+	if (!status) {
+	    try->mask->data.U8[i] = PSFTRY_MASK_PSF_FAIL;
+	    psFree (modelPSF);
+	    goto next_source;
+	}
+
+	// otherwise, save the resulting model
+	// pmModelSkyOffset (modelPSF, x, y, RADIUS);
+	try->modelPSF->data[i] = modelPSF;
+
+	// XXX : use a different aperture radius from the fit radius?
+	// XXX : use a different estimator for the local sky?
+	// XXX : pass 'source' as input?
+	if (!pmSourcePhotometry (&fitMag, &obsMag, modelPSF, source->pixels, source->mask)) {
+	    try->mask->data.U8[i] = PSFTRY_MASK_BAD_PHOT;
+	    goto next_source;
+	}	    
+
+	try->metric->data.F64[i] = obsMag - fitMag;
+	try->fitMag->data.F64[i] = fitMag;
+	Npsf ++;
+
+    next_source:
+	psImageKeepCircle (source->mask, x, y, RADIUS, "AND", ~PSPHOT_MASK_MARKED);
+
+    }
+    psLogMsg ("psphot.psftry", 4, "fit psf:   %f sec for %d sources\n", psTimerMark ("fit"), sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %d PSF candidates (PSF)\n", Npsf, sources->n);
+
+    // make this optional
+    // DumpModelFits (try->modelPSF, "modelsPSF.dat");
+
+    // XXX this function wants aperture radius for pmSourcePhotometry
+    pmPSFtryMetric_Alt (try, RADIUS);
+    psLogMsg ("psphot.pspsf", 3, "try model %s, ap-fit: %f +/- %f, sky bias: %f\n", 
+	      modelName, try->psf->ApResid, try->psf->dApResid, try->psf->skyBias);
+
+    return (try);
+}
+
+bool pmPSFtryMetric (pmPSFtry *try, float RADIUS) {
+
+  float dBin;
+  int   nKeep, nSkip;
+
+  // the measured (aperture - fit) magnitudes (dA == try->metric)
+  //   depend on both the true ap-fit (dAo) and the bias in the sky measurement:
+  //     dA = dAo + dsky/flux
+  //   where flux is the flux of the star
+  // we fit this trend to find the infinite flux aperture correction (dAo),
+  //   the nominal sky bias (dsky), and the error on dAo
+  // the values of dA are contaminated by stars with close neighbors in the aperture
+  //   we use an outlier rejection to avoid this bias
+
+  FILE *f;
+  f = fopen ("apresid.dat", "w");
+  if (f == NULL) psAbort ("pmPSFtry", "can't open output file");
+
+  // rflux = ten(0.4*fitMag);
+  psVector *rflux = psVectorAlloc (try->sources->n, PS_TYPE_F64);
+  for (int i = 0; i < try->sources->n; i++) {
+    if (try->mask->data.U8[i] & PSFTRY_MASK_ALL) continue;
+    rflux->data.F64[i] = pow(10.0, 0.4*try->fitMag->data.F64[i]);
+    fprintf (f, "%3d %8.4f %12.5e %8.4f\n", i, try->fitMag->data.F64[i], rflux->data.F64[i], try->metric->data.F64[i]);
+  }
+  fclose (f);
+
+  // XXX EAM : try 3hi/1lo sigma clipping on the rflux v dap fit
+
+  // find min and max of (1/flux):
+  psStats *stats = psStatsAlloc (PS_STAT_MIN | PS_STAT_MAX);
+  psVectorStats (stats, rflux, NULL, try->mask, PSFTRY_MASK_ALL);
+  
+  // build binned versions of rflux, metric
+  dBin = (stats->max - stats->min) / 10.0;
+  psVector *rfBin = psVectorCreate (NULL, stats->min, stats->max, dBin, PS_TYPE_F64);
+  psVector *daBin = psVectorAlloc (rfBin->n, PS_TYPE_F64);
+  psVector *maskB = psVectorAlloc (rfBin->n, PS_TYPE_U8);
+  psFree (stats);
+
+  psTrace ("psphot.metricmodel", 3, "rflux max: %g, min: %g, delta: %g\n", stats->max, stats->min, dBin);
+
+  // group data in daBin bins, measure lower 50% mean
+  for (int i = 0; i < daBin->n; i++) {
+
+    psVector *tmp = psVectorAlloc (try->sources->n, PS_TYPE_F64);
+    tmp->n = 0;
+
+    // accumulate data within bin range
+    for (int j = 0; j < try->sources->n; j++) {
+      // masked for: bad model fit, outlier in parameters
+      if (try->mask->data.U8[j] & PSFTRY_MASK_ALL) continue;
+    
+      // skip points with extreme dA values
+      if (fabs(try->metric->data.F64[j]) > 0.5) continue;
+
+      // skip points outside of this bin
+      if (rflux->data.F64[j] < rfBin->data.F64[i] - 0.5*dBin) continue;
+      if (rflux->data.F64[j] > rfBin->data.F64[i] + 0.5*dBin) continue;
+
+      tmp->data.F64[tmp->n] = try->metric->data.F64[j];
+      tmp->n ++;
+    }
+
+    // is this a valid point?
+    maskB->data.U8[i] = 0;
+    if (tmp->n < 2) {
+      maskB->data.U8[i] = 1;
+      psFree (tmp);
+      continue;
+    } 
+
+    // dA values are contaminated with low outliers 
+    // measure statistics only on upper 50% of points
+    // this would be easier if we could sort in reverse:
+
+    psVectorSort (tmp, tmp);
+    nKeep = 0.5*tmp->n;
+    nSkip = tmp->n - nKeep;
+
+    psVector *tmp2 = psVectorAlloc (nKeep, PS_TYPE_F64);
+    for (int j = 0; j < tmp2->n; j++) {
+      tmp2->data.F64[j] = tmp->data.F64[j + nSkip];
+    }
+
+    stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, tmp2, NULL, NULL, 0);
+    psTrace ("psphot.metricmodel", 4, "rfBin %d (%g): %d pts, %g\n", i, rfBin->data.F64[i], tmp->n, stats->sampleMedian);
+
+    daBin->data.F64[i] = stats->sampleMedian;
+
+    psFree (stats);
+    psFree (tmp);
+    psFree (tmp2);
+  }
+
+  // linear fit to rfBin, daBin
+  psPolynomial1D *poly = Polynomial1DAlloc_EAM (PS_POLYNOMIAL_ORD, 1);
+
+  // XXX EAM : this is the intended API (cycle 7? cycle 8?) 
+  psStats *fitstat = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+  poly = VectorClipFitPolynomial1D_EAM (poly, fitstat, maskB, 1, daBin, NULL, rfBin);
+
+  // poly = VectorFitPolynomial1D_EAM (poly, maskB, 1, daBin, NULL, rfBin);
+
+  // XXX EAM : replace this when the above version is implemented
+  // poly = VectorFitPolynomial1DOrd_EAM (poly, maskB, rfBin, daBin, NULL);
+
+  psVector *daBinFit = Polynomial1DEvalVector_EAM (poly, rfBin);
+  psVector *daResid  = (psVector *) psBinaryOp (NULL, (void *) daBin, "-", (void *) daBinFit);
+
+  stats = psStatsAlloc (PS_STAT_CLIPPED_STDEV);
+  stats = psVectorStats (stats, daResid, NULL, maskB, 1);
+
+  try->psf->ApResid = poly->coeff[0];
+  try->psf->dApResid = stats->clippedStdev;
+  try->psf->skyBias = poly->coeff[1] / (M_PI * PS_SQR(RADIUS));
+
+  psFree (rflux);
+  psFree (rfBin);
+  psFree (daBin);
+  psFree (maskB);
+  psFree (daBinFit);
+  psFree (daResid);
+  psFree (poly);
+  psFree (stats);
+  psFree (fitstat);
+
+  return true;
+}
+
+bool pmPSFtryMetric_Alt (pmPSFtry *try, float RADIUS) {
+
+  // the measured (aperture - fit) magnitudes (dA == try->metric)
+  //   depend on both the true ap-fit (dAo) and the bias in the sky measurement:
+  //     dA = dAo + dsky/flux
+  //   where flux is the flux of the star
+  // we fit this trend to find the infinite flux aperture correction (dAo),
+  //   the nominal sky bias (dsky), and the error on dAo
+  // the values of dA are contaminated by stars with close neighbors in the aperture
+  //   we use an outlier rejection to avoid this bias
+
+  // rflux = ten(0.4*fitMag);
+  psVector *rflux = psVectorAlloc (try->sources->n, PS_TYPE_F64);
+  for (int i = 0; i < try->sources->n; i++) {
+    if (try->mask->data.U8[i] & PSFTRY_MASK_ALL) continue;
+    rflux->data.F64[i] = pow(10.0, 0.4*try->fitMag->data.F64[i]);
+  }
+
+  // XXX EAM : try 3hi/1lo sigma clipping on the rflux vs metric fit
+  psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+  // XXX EAM 
+  stats->min = 1.0;
+  stats->max = 3.0;
+  stats->clipIter = 3;
+
+  // linear fit to rfBin, daBin
+  psPolynomial1D *poly = Polynomial1DAlloc_EAM (PS_POLYNOMIAL_ORD, 1);
+
+  // XXX EAM : this is the intended API (cycle 7? cycle 8?) 
+  poly = VectorClipFitPolynomial1D_EAM (poly, stats, try->mask, PSFTRY_MASK_ALL, try->metric, NULL, rflux);
+
+  fprintf (stderr, "fit stats: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+  // these stats should come back from the fit above...
+  // psVector *daFit   = Polynomial1DEvalVector_EAM (poly, rflux);
+  // psVector *daResid = (psVector *) psBinaryOp (NULL, (void *) try->metric, "-", (void *) daFit);
+
+  // stats = psStatsAlloc (PS_STAT_CLIPPED_STDEV);
+  // stats->clipIter = 3;
+  // stats->clipSigma = 3;
+
+  // stats = psVectorStats (stats, daResid, NULL, maskB, 1);
+
+  try->psf->ApResid = poly->coeff[0];
+  try->psf->dApResid = stats->sampleStdev;
+  try->psf->skyBias = poly->coeff[1] / (M_PI * PS_SQR(RADIUS));
+
+  FILE *f;
+  f = fopen ("apresid.dat", "w");
+  if (f == NULL) psAbort ("pmPSFtry", "can't open output file");
+
+  for (int i = 0; i < try->sources->n; i++) {
+    fprintf (f, "%3d %8.4f %12.5e %8.4f %3d\n", i, try->fitMag->data.F64[i], rflux->data.F64[i], try->metric->data.F64[i], try->mask->data.U8[i]);
+  }
+  fclose (f);
+
+  psFree (rflux);
+  psFree (poly);
+  psFree (stats);
+
+  // psFree (daFit);
+  // psFree (daResid);
+
+  return true;
+}
Index: /tags/psphot_dev_02/psphot/src/pmPSFtry.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmPSFtry.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmPSFtry.h	(revision 5352)
@@ -0,0 +1,31 @@
+
+# ifndef PM_PSF_TRY_H 
+# define PM_PSF_TRY_H
+
+// data to try a given PSF model type
+typedef struct {
+    pmPSF      *psf;
+    psArray    *sources;      // pointers to the original sources
+    psArray    *modelFLT;     // model fits, floating parameters 
+    psArray    *modelPSF;     // model fits, PSF parameters
+    psVector   *mask;
+    psVector   *metric;
+    psVector   *fitMag;
+} pmPSFtry;
+
+enum {
+    PSFTRY_MASK_CLEAR    = 0x00,
+    PSFTRY_MASK_OUTLIER  = 0x01, // 1: outlier in psf polynomial fit (provided by psPolynomials)
+    PSFTRY_MASK_FLT_FAIL = 0x02, // 2: flt model failed to converge 
+    PSFTRY_MASK_PSF_FAIL = 0x04, // 3: psf model failed to converge 
+    PSFTRY_MASK_BAD_PHOT = 0x08, // 4: invalid source photometry	   
+    PSFTRY_MASK_ALL      = 0x0f,
+} pmPSFtryMaskValues;
+
+// pmPSFtry utilities
+pmPSFtry    *pmPSFtryAlloc (psArray *stars, char *modelName);
+pmPSFtry    *pmPSFtryModel (psArray *sources, char *modelName, float radius);
+bool	     pmPSFtryMetric (pmPSFtry *try, float RADIUS);
+bool	     pmPSFtryMetric_Alt (pmPSFtry *try, float RADIUS);
+
+# endif
Index: /tags/psphot_dev_02/psphot/src/pmPeaksSigmaLimit.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/pmPeaksSigmaLimit.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/pmPeaksSigmaLimit.c	(revision 5352)
@@ -0,0 +1,43 @@
+# include "psphot.h"
+
+psArray *pmPeaksSigmaLimit (eamReadout *imdata, psMetadata *config, psStats *sky) 
+{
+    bool  status = false;
+    float NSIGMA;
+    float SIGMA;
+    float threshold;
+
+    // smooth the image 
+
+    psTimerStart ("psphot");
+
+    SIGMA  = psMetadataLookupF32 (&status, config, "PEAKS_SMOOTH_SIGMA");
+    NSIGMA = psMetadataLookupF32 (&status, config, "PEAKS_SMOOTH_NSIGMA");
+
+    psImage *smooth = psImageCopy (NULL, imdata->image, PS_TYPE_F32);
+    psImageSmooth_EAM (smooth, SIGMA, NSIGMA);
+    psLogMsg ("psphot", 4, "smooth: %f sec\n", psTimerMark ("psphot"));
+
+    psTimerStart ("psphot");
+
+    // set peak threshold
+    NSIGMA    = psMetadataLookupF32 (&status, config, "PEAKS_NSIGMA_LIMIT");
+    threshold = NSIGMA*sky->sampleStdev + sky->sampleMean;
+    psLogMsg ("psphot", 3, "threshold: %f DN\n", threshold);
+
+    // find the peaks in the smoothed image 
+    psArray *peaks = pmFindImagePeaks (smooth, threshold);
+    if (peaks == NULL) psAbort ("find peaks", "no peaks found");
+    psFree (smooth);
+
+    // make this optional
+    // DumpPeaks (peaks, "peaks.dat");
+
+    psLogMsg ("psphot", 3, "%d peaks: %f sec\n", peaks->n, psTimerMark ("psphot"));
+
+    return (peaks);
+}
+
+// In this function, we smooth the image, then search for the peaks 
+// Should we also subtract a super-binned image? (as an option?)
+// We need to gracefully handle no source detections
Index: /tags/psphot_dev_02/psphot/src/psEllipse.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psEllipse.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psEllipse.c	(revision 5352)
@@ -0,0 +1,59 @@
+# include "pslib.h"
+# include "psEllipse.h"
+
+EllipseAxes EllipseMomentsToAxes (EllipseMoments moments) {
+
+  EllipseAxes axes;
+
+  double f = sqrt (0.25*PS_SQR(moments.x2 - moments.y2) + PS_SQR(moments.xy));
+  if (f > (moments.x2 + moments.y2) / 2.0) {
+    f = 0.98*(moments.x2 + moments.y2) / 2.0;
+  }
+
+  axes.major = sqrt (0.5*(moments.x2 + moments.y2) + f);
+  axes.minor = sqrt (0.5*(moments.x2 + moments.y2) - f);
+  axes.theta = atan2 (2*moments.xy, moments.x2 - moments.y2) / 2;
+  // theta in radians
+
+  return (axes);
+}
+
+EllipseShape EllipseAxesToShape (EllipseAxes axes) {
+
+  EllipseShape shape;
+
+  double r1 = 1.0 / PS_SQR(axes.major) + 1.0 / PS_SQR(axes.minor);
+  double r2 = 1.0 / PS_SQR(axes.major) - 1.0 / PS_SQR(axes.minor);
+
+  double sxr = r1 + r2*cos(2*axes.theta);
+  double syr = r1 - r2*cos(2*axes.theta);
+
+  shape.sx = 1.0 / sqrt(sxr);
+  shape.sy = 1.0 / sqrt(syr);
+  shape.sxy = r2*sin(2*axes.theta);
+
+  return (shape);
+}
+
+EllipseAxes EllipseShapeToAxes (EllipseShape shape) {
+
+  EllipseAxes axes;
+
+  double f1 = 1.0 / PS_SQR(shape.sx) + 1.0 / PS_SQR(shape.sy);
+  double f2 = 1.0 / PS_SQR(shape.sx) - 1.0 / PS_SQR(shape.sy);
+
+  // force the axis ratio to be less than 10
+  double r1 = 0.5*0.95*sqrt (PS_SQR(f1) - PS_SQR(f2));
+
+  shape.sxy = PS_MIN(PS_MAX(shape.sxy, -r1), r1);
+
+  axes.theta = atan2 (-2.0*shape.sxy, f2) / 2.0;
+
+  double Ar = 0.25*f1 + 0.25*sqrt(PS_SQR(f2) + 4*PS_SQR(shape.sxy));
+  double Br = 0.25*f1 - 0.25*sqrt(PS_SQR(f2) + 4*PS_SQR(shape.sxy));
+
+  axes.minor = 1.0 / sqrt (Ar);
+  axes.major = 1.0 / sqrt (Br);
+
+  return (axes);
+}
Index: /tags/psphot_dev_02/psphot/src/psEllipse.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/psEllipse.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psEllipse.h	(revision 5352)
@@ -0,0 +1,24 @@
+// strucures to define elliptical shape parameters
+typedef struct {
+    double major;
+    double minor;
+    double theta;
+} EllipseAxes;
+
+typedef struct {
+    double x2;
+    double y2;
+    double xy;
+} EllipseMoments;
+
+typedef struct {
+    double sx;
+    double sy;
+    double sxy;
+} EllipseShape;
+
+// conversions between elliptical shape representations
+EllipseAxes EllipseMomentsToAxes (EllipseMoments moments);
+EllipseShape EllipseAxesToShape (EllipseAxes axes);
+EllipseAxes EllipseShapeToAxes (EllipseShape shape);
+
Index: /tags/psphot_dev_02/psphot/src/psImageData.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psImageData.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psImageData.c	(revision 5352)
@@ -0,0 +1,24 @@
+# include "psphot.h"
+
+static void eamReadoutFree (eamReadout *imdata) {
+
+  if (imdata == NULL) return;
+
+  psFree (imdata->image);
+  psFree (imdata->header);
+  psFree (imdata->weight);
+  psFree (imdata->mask);
+  return;
+}
+
+eamReadout *eamReadoutAlloc (psImage *image, psImage *weight, psImage *mask, psMetadata *header) {
+  
+    eamReadout *imdata = psAlloc(sizeof(eamReadout));
+    imdata->image = image;
+    imdata->header = header;
+    imdata->weight = weight;
+    imdata->mask = mask;
+
+    psMemSetDeallocator(imdata, (psFreeFunc) eamReadoutFree);
+    return (imdata);
+}
Index: /tags/psphot_dev_02/psphot/src/psLibUtils.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psLibUtils.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psLibUtils.c	(revision 5352)
@@ -0,0 +1,476 @@
+# include <strings.h>  // for strncasecmp
+# include <pslib.h>
+# include "psLibUtils.h"
+
+// XXX EAM : this is NOT included in the C99 headers ??
+FILE *fdopen(int fildes, const char *mode);
+
+// XXX EAM : these utility functions should be added back into PSLib
+
+# if (0) // now in psLib (v8)
+static psHash *timers = NULL;
+
+// start/restart a named timer
+bool psTimerStart (char *name) {
+
+    psTime *start;
+
+    if (timers == NULL) timers = psHashAlloc (16);
+
+    start = psTimeGetNow (PS_TIME_UTC);
+    psHashAdd (timers, name, start);
+    psFree (start); 
+    return (TRUE);
+}
+
+bool psTimerClear (char *name) {
+
+  bool status;
+
+  if (name == NULL) return false;
+
+  status = psHashRemove (timers, name);
+  return (status);
+}
+
+// get current elapsed time on named timer
+psF64 psTimerMark (char *name) {
+
+    psTime *start;
+    psTime *mark;
+    psF64   delta;
+
+    if (timers == NULL) return (0);
+
+    start = psHashLookup (timers, name);
+    if (start == NULL) return (0);
+
+    mark = psTimeGetNow (PS_TIME_UTC);
+    delta = psTimeDelta (mark, start);
+    psFree (mark);
+    // psFree (start); -- XXX is psHashLookup not psMemCopying?
+
+    return (delta);
+}
+
+void psTimerFree () {
+
+  psFree (timers);
+  p_psTimeFinalize();
+  return;
+}
+# endif
+
+# if (0)
+// find the location of the specified argument
+int psArgumentGet (int argc, char **argv, char *arg) {
+
+    int i;
+
+    for (i = 0; i < argc; i++) {
+	if (!strcmp(argv[i], arg))
+	    return (i);
+    }
+  
+    return ((int)NULL);
+}
+
+// remove the specified argument (by location)
+int psArgumentRemove (int N, int *argc, char **argv) {
+
+    int i;
+
+    if ((N != (int)NULL) && (N != 0)) {
+	(*argc)--;
+	for (i = N; i < *argc; i++) {
+	    argv[i] = argv[i+1];
+	}
+    }
+
+    return (N);
+}
+# endif
+
+// we have log levels 1 (Error), 2 (Warning), 3 (Info), 4 (Details), 5 (Minutiae)
+// 2 = default, -v = 3, -vv = 4, -vvv = 5 
+psS32 psLogArguments (int *argc, char **argv) {
+  
+    int N, level;
+
+    // default log level is 2
+    level = 2;
+
+    // set in order, so that -vvv overrides -vv overrides -v
+    if ((N = psArgumentGet (*argc, argv, "-v"))) {
+	psArgumentRemove (N, argc, argv);
+	level = 3;
+    }
+    if ((N = psArgumentGet (*argc, argv, "-vv"))) {
+	psArgumentRemove (N, argc, argv);
+	level = 4;
+    }
+    if ((N = psArgumentGet (*argc, argv, "-vvv"))) {
+	psArgumentRemove (N, argc, argv);
+	level = 5;
+    }
+
+    if ((N = psArgumentGet (*argc, argv, "-logfmt"))) {
+	if (*argc < N + 2) {
+	    psAbort ("psLogArguments", "USAGE: -logfmt (format)");
+	}
+	psArgumentRemove (N, argc, argv);
+	psLogSetFormat (argv[N]); // XXX EAM : this function should return an error if the log format is invalid
+	psArgumentRemove (N, argc, argv);
+    }
+
+    // set the level, return the level
+    psLogSetLevel (level);
+    return (level);
+}
+
+// set trace levels by facility
+psS32 psTraceArguments (int *argc, char **argv) {
+  
+    int N;
+
+    // argument format is: -trace (facil) (level) 
+    while ((N = psArgumentGet (*argc, argv, "-trace"))) {
+	if (*argc < N + 3) {
+	    psAbort ("psTraceArguments", "USAGE: -trace (facility) (level)");
+	}
+	psArgumentRemove (N, argc, argv);
+	psTraceSetLevel (argv[N], atoi(argv[N+1]));
+	psArgumentRemove (N, argc, argv);
+	psArgumentRemove (N, argc, argv);
+    }
+    if ((N = psArgumentGet (*argc, argv, "-trace-levels"))) {
+	psTracePrintLevels ();
+	exit (2);
+    }
+    return (TRUE);
+}
+
+# if (0)
+// alternate implementation of this function from pmObjects.c
+// now defined in psLib SDRS as psImageRow
+psVector *psGetRowVectorFromImage(psImage *image,
+				  psU32 row)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+
+    psVector *tmpVector = psVectorAlloc(image->numCols, PS_TYPE_F32);
+    memcpy (tmpVector->data.F32, image->data.F32[row], image->numCols*sizeof(psF32));
+    return(tmpVector);
+}
+# endif
+
+// XXX EAM : this is now in psLib
+void psImageSmooth_EAM (psImage *image, float sigma, float Nsigma) {
+
+    int Nx, Ny, Npixel, Nrange;
+    float factor, g, s;
+    psVector *temp;
+
+    // relevant terms
+    Nrange = sigma*Nsigma + 0.5;
+    Npixel = 2*Nrange + 1;
+    factor = -0.5/(sigma*sigma);
+
+    Nx = image->numCols;
+    Ny = image->numRows;
+
+    // generate gaussian
+    psVector *gaussnorm = psVectorAlloc (Npixel, PS_TYPE_F32);
+    for (int i = -Nrange; i < Nrange + 1; i++) {
+	gaussnorm->data.F32[i+Nrange] = exp (factor*i*i);
+    }
+    psF32 *gauss = &gaussnorm->data.F32[Nrange];
+
+    // smooth in X direction
+    temp = psVectorAlloc (Nx, PS_TYPE_F32);
+    for (int j = 0; j < Ny; j++) {
+	psF32 *vi = image->data.F32[j];
+	psF32 *vo = temp->data.F32;
+	for (int i = 0; i < Nx; i++) {
+	    g = s = 0;
+	    for (int n = -Nrange; n < Nrange + 1; n++) {
+		if (i+n < 0) continue;
+		if (i+n >= Nx) continue;
+		s += gauss[n]*vi[i+n];
+		g += gauss[n];
+	    }
+	    vo[i] = s / g;
+	}
+	memcpy (image->data.F32[j], temp->data.F32, Nx*sizeof(psF32));
+    }
+    psFree (temp);
+
+    // smooth in Y direction
+    temp = psVectorAlloc (image->numRows, PS_TYPE_F32);
+    for (int i = 0; i < Nx; i++) {
+	psF32  *vo = temp->data.F32;
+	psF32 **vi = image->data.F32;
+	for (int j = 0; j < Ny; j++) {
+	    g = s = 0;
+	    for (int n = -Nrange; n < Nrange + 1; n++) {
+		if (j+n < 0) continue;
+		if (j+n >= Ny) continue;
+		s += gauss[n]*vi[j+n][i];
+		g += gauss[n];
+	    }
+	    vo[j] = s / g;
+	}
+	// replace temp in image
+	for (int j = 0; j < Ny; j++) {
+	    vi[j][i] = vo[j];
+	}
+    }
+    psFree (temp);
+    psFree (gaussnorm);
+}
+    
+bool psImageInit (psImage *image,...) {
+
+  va_list argp;
+  psU8  vU8;
+  psF32 vF32;
+  psF64 vF64;
+
+  if (image == NULL) return (false);
+
+  va_start (argp, image);
+
+  switch (image->type.type) {
+    case PS_TYPE_U8:
+      vU8 = va_arg (argp, psU32);
+
+      for (int iy = 0; iy < image->numRows; iy++) {
+	for (int ix = 0; ix < image->numCols; ix++) {
+	  image->data.U8[iy][ix] = vU8;
+	}
+      }
+      break;
+
+    case PS_TYPE_F32:
+      vF32 = va_arg (argp, psF64);
+      
+      for (int iy = 0; iy < image->numRows; iy++) {
+	for (int ix = 0; ix < image->numCols; ix++) {
+	  image->data.F32[iy][ix] = vF32;
+	}
+      }
+      return (true);
+
+    case PS_TYPE_F64:
+      vF64 = va_arg (argp, psF64);
+
+      for (int iy = 0; iy < image->numRows; iy++) {
+	for (int ix = 0; ix < image->numCols; ix++) {
+	  image->data.F64[iy][ix] = vF64;
+	}
+      }
+      return (true);
+
+    default:
+      psAbort ("psphot.psUtils", "datatype %d not defined in psImageInit\n", image->type);
+      return (false);
+  }
+  return (false);
+}	    
+
+// now in psLib (v8)
+// count number of pixels with given mask value
+// XXX EAM : version in psLib is broken
+int psImageCountPixelMask_EAM (psImage *mask, psU8 value) 
+{
+    int Npixels = 0;
+  
+    for (int i = 0; i < mask->numRows; i++) {
+	for (int j = 0; j < mask->numCols; j++) {
+	    if (mask->data.U8[i][j] & value) {
+		Npixels ++;
+	    }
+	}
+    }
+    return (Npixels);
+}
+
+// define a square region centered on the given coordinate
+// XXX EAM : this is now in psLib
+# if (0)
+psRegion *psRegionSquare (psF32 x, psF32 y, psF32 radius) {
+    psRegion *region;
+    region = psRegionAlloc (x - radius, x + radius + 1,    
+			    y - radius, y + radius + 1);
+    return (region);
+}
+# endif
+
+// set actual region based on image parameters:
+// compensate for negative upper limits
+// XXX this is inconsistent: the coordindates should always be in the parent
+//     frame, which means the negative values should subtract from Nx,Ny of
+//     the parent, not the child.  but, we don't carry the dimensions of the 
+//     parent in the psImage container.  for now, use the child Nx,Ny
+//     force range to be on this subimage 
+// XXX EAM : this needs to be changed to use psRegion rather than psRegion*
+// XXX EAM : this is now in psLib
+# if (0)
+psRegion *psRegionForImage (psRegion *out, psImage *image, psRegion *in) {
+    
+    // x0,y0, x1,y1 are in *parent* units
+
+    if (out == NULL) {
+	out = psRegionAlloc(in->x0, in->x1, in->y0, in->y1);
+    } else {
+	*out = *in;
+    }
+    // XXX these are probably wrong (see above)
+    if (out->x1 <= 0) {
+	out->x1 = image->col0 + image->numCols + out->x1;
+    }
+    if (out->y1 <= 0) {
+	out->y1 = image->row0 + image->numRows + out->y1;
+    }
+
+    // force the lower-limits to be on the child
+    out->x0 = PS_MAX(image->col0, out->x0);
+    out->y0 = PS_MAX(image->row0, out->y0);
+
+    // force the upper-limits to be on the child
+    out->x1 = PS_MIN(image->col0 + image->numCols, out->x1);
+    out->y1 = PS_MIN(image->row0 + image->numRows, out->y1);
+    return (out);
+}
+# endif
+
+// mask the area contained by the region
+// the region is defined wrt the parent image
+// XXX EAM : this is now in psLib
+# if (0)
+void psImageMaskRegion (psImage *image, psRegion *region, bool logical_and, int maskValue) {
+
+    for (int iy = 0; iy < image->numRows; iy++) {
+	for (int ix = 0; ix < image->numCols; ix++) {
+	    if (ix + image->col0 <  region->x0) continue;
+	    if (ix + image->col0 >= region->x1) continue;
+	    if (iy + image->row0 <  region->y0) continue;
+	    if (iy + image->row0 >= region->y1) continue;
+	    if (logical_and) {
+		image->data.U8[iy][ix] &= maskValue;
+	    } else {
+		image->data.U8[iy][ix] |= maskValue;
+	    }
+	}
+    }
+}
+# endif
+
+// mask the area not contained by the region
+// the region is defined wrt the parent image
+// XXX EAM : this is now in psLib
+# if (0)
+void psImageKeepRegion (psImage *image, psRegion *region, bool logical_and, int maskValue) {
+
+    for (int iy = 0; iy < image->numRows; iy++) {
+	for (int ix = 0; ix < image->numCols; ix++) {
+	    if (ix + image->col0 <  region->x0) goto maskit;
+	    if (ix + image->col0 >= region->x1) goto maskit;
+	    if (iy + image->row0 <  region->y0) goto maskit;
+	    if (iy + image->row0 >= region->y1) goto maskit;
+	    continue;
+	maskit:
+	    if (logical_and) {
+		image->data.U8[iy][ix] &= maskValue;
+	    } else {
+		image->data.U8[iy][ix] |= maskValue;
+	    }
+	}
+    }
+}
+# endif
+
+// mask the area contained by the region
+// the region is defined wrt the parent image
+// XXX EAM : this is now in psLib
+# if (0)
+void psImageMaskCircle (psImage *image, double x, double y, double radius, bool logical_and, int maskValue) {
+
+    double dx, dy, r2, R2;
+
+    R2 = PS_SQR(radius);
+
+    for (int iy = 0; iy < image->numRows; iy++) {
+	for (int ix = 0; ix < image->numCols; ix++) {
+	    dx = ix + image->col0 - x;
+	    dy = iy + image->row0 - y;
+	    r2 = PS_SQR(dx) + PS_SQR(dy);
+	    if (r2 > R2) continue;
+	    if (logical_and) {
+		image->data.U8[iy][ix] &= maskValue;
+	    } else {
+		image->data.U8[iy][ix] |= maskValue;
+	    }
+	}
+    }
+}
+# endif
+
+// mask the area contained by the region
+// the region is defined wrt the parent image
+// XXX EAM : this is now in psLib
+# if (0)
+void psImageKeepCircle (psImage *image, double x, double y, double radius, bool logical_and, int maskValue) {
+
+    double dx, dy, r2, R2;
+
+    R2 = PS_SQR(radius);
+
+    for (int iy = 0; iy < image->numRows; iy++) {
+	for (int ix = 0; ix < image->numCols; ix++) {
+	    dx = ix + image->col0 - x;
+	    dy = iy + image->row0 - y;
+	    r2 = PS_SQR(dx) + PS_SQR(dy);
+	    if (r2 < R2) continue;
+	    if (logical_and) {
+		image->data.U8[iy][ix] &= maskValue;
+	    } else {
+		image->data.U8[iy][ix] |= maskValue;
+	    }
+	}
+    }
+}
+# endif
+
+# if (0)
+psVector *psVectorCreate (double lower, double upper, double delta, psElemType type) {
+
+  int nBin = (upper - lower) / delta;
+
+  psVector *out = psVectorAlloc (nBin, type);
+  
+  for (int i = 0; i < nBin; i++) {
+    out->data.F64[i] = lower + i * delta;
+  }
+
+  return (out);
+}
+# endif
+
+// XXX EAM a utility function
+bool p_psVectorPrintRow (int fd, psVector *a, char *name)
+{
+
+    FILE *f;
+    f = fdopen(fd, "a+");
+    fprintf (f, "vector: %s\n", name);
+
+    for (int i = 0; i < a[0].n; i++) {
+        fprintf (f, "%f  ", p_psVectorGetElementF64(a, i));
+    }
+    fprintf (f, "\n");
+    fclose(f);
+    return (true);
+}
+// XXX EAM is the use of fdopen here a good way to do this?
Index: /tags/psphot_dev_02/psphot/src/psLibUtils.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/psLibUtils.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psLibUtils.h	(revision 5352)
@@ -0,0 +1,77 @@
+
+# ifndef PS_LIB_UTILS
+# define PS_LIB_UTILS
+
+// XXX EAM : psEllipse needs to be be included in psLib
+# include "psEllipse.h"
+
+// structure to carry a dynamic string
+typedef struct {
+    int NLINE;
+    int Nline;
+    char *line;
+} psLine;
+
+# define psMemCopy(A)(psMemIncrRefCounter((A)))
+
+// XXX EAM : my version using varience instead of stdev
+bool psMinimizeGaussNewtonDelta_EAM (psVector *delta,
+				     const psVector *params,
+				     const psVector *paramMask,
+				     const psArray  *x,
+				     const psVector *y,
+				     const psVector *yErr,
+				     psMinimizeLMChi2Func func);
+
+// minimize : using varience vs sigma and parameter limits
+psBool 	     p_psMinLM_GuessABP_EAM (psImage  *Alpha, psVector *Beta, psVector *Params, const psImage  *alpha, const psVector *beta, const psVector *params, const psVector *paramMask, const psVector *beta_lim, const psVector *params_min, const psVector *params_max, psF64 lambda);
+psBool 	     psMinimizeLMChi2_EAM(psMinimization *min, psImage *covar, psVector *params, const psVector *paramMask, const psArray *x, const psVector *y, const psVector *yErr, psMinimizeLMChi2Func func);
+psF64        p_psMinLM_dLinear (const psVector *Beta, const psVector *beta, psF64 lambda);
+
+int          psImageCountPixelMask_EAM (psImage *mask, psU8 value); // added to SDRS
+
+// psLib extra utilities
+# if (0) // added to psLib v8
+int	     psArgumentGet (int argc, char **argv, char *arg); // added to SDRS
+int	     psArgumentRemove (int N, int *argc, char **argv); // added to SDRS
+bool 	     psTimerClear (char *name);   // added to SDRS 
+psVector    *psVectorCreate (double lower, double upper, double delta, psElemType type); // added to SDRS
+# endif
+
+bool 	     psTimerStart (char *name);   // added to SDRS
+psF64 	     psTimerMark (char *name);    // added to SDRS
+void 	     psTimerFree ();              // added to SDRS (as psTimerStop)
+psS32 	     psLogArguments (int *argc, char **argv);   // added to SDRS (part of psArgumentVerbosity)
+psS32 	     psTraceArguments (int *argc, char **argv); // added to SDRS (part of psArgumentVerbosity)
+
+// basic image functions
+bool         psImageInit (psImage *image,...); // added to SDRS (v.15)
+void	     psImageSmooth_EAM (psImage *image, float sigma, float Nsigma); // added to SDRS (v.15)
+
+// psLine functions -- keep out for now?
+psLine      *psLineAlloc (int Nline);
+bool	     psLineInit (psLine *line);
+bool	     psLineAdd (psLine *line, char *format, ...);
+
+// not included in the .h file -- these are fairly lame, keep out?
+bool p_psVectorPrint (int fd, psVector *a, char *name);
+bool p_psVectorPrintRow (int fd, psVector *a, char *name);
+
+// polynomial functions -- all in SDRS, not done for v.15
+// XXX the pslib 0.7.0 polynomials are *still* nTerm, not nOrder!!!
+psPolynomial1D *Polynomial1DAlloc_EAM(psPolynomialType type, psS32 nOrder);
+void	        Polynomial1DDump_EAM(psPolynomial1D *poly);
+psF64		Polynomial1DEval_EAM(const psPolynomial1D* myPoly, psF64 x);
+psVector       *Polynomial1DEvalVector_EAM(const psPolynomial1D *myPoly, const psVector *x);
+psPolynomial1D *VectorFitPolynomial1D_EAM(psPolynomial1D* myPoly, psVector* mask, psMaskType maskValue, const psVector* y, const psVector* yErr, const psVector* x);
+psPolynomial1D *VectorClipFitPolynomial1D_EAM(psPolynomial1D* poly, psStats *stats, psVector* mask, psMaskType maskValue, const psVector* z, const psVector* zErr, const psVector* x);
+
+psPolynomial2D *Polynomial2DAlloc_EAM(psPolynomialType type, psS32 nXorder, psS32 nYorder);
+void	        Polynomial2DDump_EAM(psPolynomial2D *poly);
+psF64	        Polynomial2DEval_EAM(const psPolynomial2D* myPoly, psF64 x, psF64 y);
+psVector       *Polynomial2DEvalVector_EAM(const psPolynomial2D *myPoly, const psVector *x,const psVector *y);
+psVector       *Polynomial2DEvalVectorD_EAM(const psPolynomial2D *myPoly, const psVector *x,const psVector *y);
+psPolynomial2D *VectorFitPolynomial2D_EAM(psPolynomial2D* myPoly, psVector* mask, psMaskType maskValue, const psVector* z, const psVector* zErr, const psVector* x, const psVector* y);
+psPolynomial2D *VectorClipFitPolynomial2D_EAM(psPolynomial2D* poly, psStats *stats, psVector* mask, psMaskType maskValue, const psVector* z, const psVector* zErr, const psVector* x, const psVector* y);
+
+# endif
Index: /tags/psphot_dev_02/psphot/src/psLine.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psLine.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psLine.c	(revision 5352)
@@ -0,0 +1,47 @@
+# include <pslib.h>
+# include "psLibUtils.h"
+
+static void psLineFree (psLine *line) {
+
+  if (line == NULL) return;
+
+  psFree (line->line);
+  return;
+}
+
+// allocate a psLine structrue
+psLine *psLineAlloc (int Nline) {
+
+  psLine *line;
+  line = psAlloc (sizeof(psLine));
+  psMemSetDeallocator(line, (psFreeFunc) psLineFree);
+
+  line->Nline = 0;
+  line->NLINE = Nline;
+  line->line = psAlloc (Nline);
+  return (line);
+}
+
+bool psLineInit (psLine *line) {
+  if (line == NULL) return (false);
+  line->Nline = 0;
+  return (true);
+}
+
+bool psLineAdd (psLine *line, char *format, ...) {
+
+    int Nchar;
+    va_list ap;
+
+    if (line == NULL) return (false);
+
+    int nMax = line->NLINE - line->Nline;
+
+    va_start (ap, format);
+    Nchar = vsnprintf (&line->line[line->Nline], nMax, format, ap);
+    line->Nline += PS_MIN (nMax - 1, Nchar);
+
+    if (Nchar >= nMax) return (false);
+    return (true);
+}
+
Index: /tags/psphot_dev_02/psphot/src/psMinimize.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psMinimize.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psMinimize.c	(revision 5352)
@@ -0,0 +1,302 @@
+# include <pslib.h>
+# include "psLibUtils.h"
+
+// XXX EAM : this file contains my alternate implementations of psMinimizeLMChi2
+// XXX EAM this implementation of MinLM includes limits on params & dparams
+
+psBool p_psMinLM_GuessABP(
+			  psImage  *Alpha,
+			  psVector *Beta,
+			  psVector *Params,
+			  const psImage  *alpha,
+			  const psVector *beta,
+			  const psVector *params,
+			  const psVector *paramMask,
+			  const psVector *beta_lim,
+			  const psVector *params_min,
+			  const psVector *params_max,
+			  psF64 lambda);
+
+psBool psMinimizeLMChi2_EAM(psMinimization *min,
+			    psImage *covar,
+			    psVector *params,
+			    const psVector *paramMask,
+			    const psArray *x,
+			    const psVector *y,
+			    const psVector *yWt,
+			    psMinimizeLMChi2Func func)
+{
+    // PS_PTR_CHECK_NULL(min, NULL);
+    // PS_VECTOR_CHECK_NULL(params, NULL);
+    // PS_VECTOR_CHECK_EMPTY(params, NULL);
+    // PS_PTR_CHECK_NULL(x, NULL);
+    // PS_VECTOR_CHECK_NULL(y, NULL);
+    // PS_VECTOR_CHECK_EMPTY(y, NULL);
+    // PS_VECTOR_CHECK_SIZE_EQUAL(x, y, NULL);
+    // PS_PTR_CHECK_NULL(func, NULL);
+
+    // this function has test and current values for several things
+    // the current best value is in lower case
+    // the next guess value is in upper case
+
+    // allocate internal arrays (current vs Guess)
+    psImage *alpha   = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+    psImage *Alpha   = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+    psVector *beta   = psVectorAlloc (params->n, PS_TYPE_F64);
+    psVector *Beta   = psVectorAlloc (params->n, PS_TYPE_F64);
+    psVector *Params = psVectorAlloc (params->n, PS_TYPE_F32);
+    psVector *dy     = NULL;
+    psF64 Chisq = 0.0;
+    psF64 lambda = 0.001;
+
+    psVector *beta_lim = NULL;
+    psVector *param_min = NULL;
+    psVector *param_max = NULL;
+
+    // if we are provided a covar image, we expect to find these three vectors in first three rows
+    if (covar != NULL) {
+	beta_lim  = psVectorAlloc (params->n, PS_TYPE_F32);
+	param_min = psVectorAlloc (params->n, PS_TYPE_F32);
+	param_max = psVectorAlloc (params->n, PS_TYPE_F32);
+	for (int i = 0; i < params->n; i++) {
+	    beta_lim->data.F32[i] = covar->data.F64[0][i];
+	    param_min->data.F32[i] = covar->data.F64[1][i];
+	    param_max->data.F32[i] = covar->data.F64[2][i];
+	}
+	psImageRecycle (covar, params->n, params->n, PS_TYPE_F64);
+    }
+	
+
+    // why is this needed here??? the initial guess on params is provided by the user
+    Params = psVectorCopy (Params, params, PS_TYPE_F32);
+
+    // the user provides the error or NULL.  we need to convert
+    // to appropriate weights
+    if (yWt != NULL) {
+	dy = (psVector *) yWt;
+    } else {
+	dy = psVectorAlloc (y->n, PS_TYPE_F32);
+	psVectorInit (dy, 1.0);
+//	for (int i = 0; i < dy->n; i++) {
+//            dy->data.F32[i] = 1.0;
+//        }
+    }
+
+    // calculate initial alpha and beta, set chisq (min->value)
+    min->value = p_psMinLM_SetABX (alpha, beta, params, paramMask, x, y, dy, func);
+    if (isnan(min->value)) {
+	min->iter = min->maxIter;
+	return (false);
+    }
+# ifndef PS_NO_TRACE
+    // dump some useful info if trace is defined
+    if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") >= 5) {
+        p_psImagePrint  (psTraceGetDestination(), alpha, "alpha guess");
+        p_psVectorPrint (psTraceGetDestination(), beta, "beta guess");
+        p_psVectorPrint (psTraceGetDestination(), params, "params guess");
+    }
+    if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") == 4) {
+	p_psVectorPrintRow (psTraceGetDestination(), Params, "params guess");
+    }
+# endif /* PS_NO_TRACE */
+
+    // iterate until the tolerance is reached, or give up
+    while ((min->iter < min->maxIter) && ((min->lastDelta > min->tol) || !isfinite(min->lastDelta))) {
+
+        // set a new guess for Alpha, Beta, Params
+        p_psMinLM_GuessABP_EAM (Alpha, Beta, Params, alpha, beta, params, paramMask, beta_lim, param_min, param_max, lambda);
+
+        // measure linear model prediction
+        psF64 dLinear = p_psMinLM_dLinear (Beta, beta, lambda);
+
+# ifndef PS_NO_TRACE
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") >= 5) {
+            p_psImagePrint  (psTraceGetDestination(), Alpha, "alpha guess");
+            p_psVectorPrint (psTraceGetDestination(), Beta, "beta guess");
+            p_psVectorPrint (psTraceGetDestination(), Params, "params guess");
+        }
+        if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") == 4) {
+            p_psVectorPrintRow (psTraceGetDestination(), Params, "params guess");
+        }
+# endif /* PS_NO_TRACE */
+
+        // calculate Chisq for new guess, update Alpha & Beta
+        Chisq = p_psMinLM_SetABX (Alpha, Beta, Params, paramMask, x, y, dy, func);
+
+	// XXX EAM alternate convergence criterion:
+	// compare the delta (min->value - Chisq) with the
+	// expected delta from the linear model (dLinear)
+        // accept new guess (if improvement), or increase lambda
+	psF64 rho = (min->value - Chisq) / dLinear;
+
+        psTrace (".psLib.dataManip.psMinimizeLMChi2", 4, "last chisq: %f, new chisq %f, delta: %f, rho: %f\n", min->value, Chisq, min->lastDelta, rho);
+# ifndef PS_NO_TRACE
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") >= 5) {
+            p_psImagePrint  (psTraceGetDestination(), Alpha, "alpha guess");
+            p_psVectorPrint (psTraceGetDestination(), Beta, "beta guess");
+            p_psVectorPrint (psTraceGetDestination(), Params, "params guess");
+        }
+# endif /* PS_NO_TRACE */
+
+        /* if (Chisq < min->value) {  */
+	if (rho > 0.0) {  
+            min->lastDelta = (min->value - Chisq) / (dy->n - params->n);
+            min->value = Chisq;
+            alpha  = psImageCopy (alpha, Alpha, PS_TYPE_F64);
+            beta   = psVectorCopy (beta, Beta, PS_TYPE_F64);
+            params = psVectorCopy (params, Params, PS_TYPE_F32);
+	    lambda *= 0.1;
+        } else {
+            lambda *= 10.0;
+        }
+        min->iter ++;
+    }
+    psTrace (".psLib.dataManip.psMinimizeLMChi2", 3, "chisq: %f, last delta: %f, Niter: %d\n", min->value, min->lastDelta, min->iter);
+
+    // construct & return the covariance matrix (if requested)
+    if (covar != NULL) {
+	p_psMinLM_GuessABP_EAM (covar, Beta, Params, alpha, beta, params, paramMask, beta_lim, param_min, param_max, 0.0);
+    }      
+
+    // free the internal temporary data
+    psFree (alpha);
+    psFree (Alpha);
+    psFree (beta);
+    psFree (Beta);
+    psFree (Params);
+    if (yWt == NULL) {
+	psFree (dy);
+    }
+
+    if (min->iter == min->maxIter) {
+	return (false);
+    } 
+    return (true);
+}
+
+// XXX EAM : can we use static copies of LUv, LUm, A?
+psBool p_psMinLM_GuessABP_EAM (psImage  *Alpha,
+			       psVector *Beta,
+			       psVector *Params,
+			       const psImage  *alpha,
+			       const psVector *beta,
+			       const psVector *params,
+			       const psVector *paramMask,
+			       const psVector *beta_lim,
+			       const psVector *params_min,
+			       const psVector *params_max,
+			       psF64 lambda)
+{
+
+# define USE_LU_DECOMP 1
+# if (USE_LU_DECOMP)
+    psVector *LUv = NULL;
+    psImage  *LUm = NULL;
+    psImage  *A   = NULL;
+    psF32    det;
+
+    // LU decomposition version
+    psTrace (".psLib.dataManip.psMinLM_GuessABP", 5, "using LUD version\n");
+
+    // set new guess values (creates matrix A)
+    A = psImageCopy (NULL, alpha, PS_TYPE_F64);
+    for (int j = 0; j < params->n; j++) {
+	if ((paramMask != NULL) && (paramMask->data.U8[j])) continue;
+        A->data.F64[j][j] = alpha->data.F64[j][j] * (1.0 + lambda);
+    }
+
+    // solve A*beta = Beta (Alpha = 1/A)
+    // these operations do not modify the input values (creates LUm, LUv)
+    LUm   = psMatrixLUD (NULL, &LUv, A);
+    Beta  = psMatrixLUSolve (Beta, LUm, beta, LUv);
+    Alpha = psMatrixInvert (Alpha, A, &det);
+
+# else
+    // gauss-jordan version
+    psTrace (".psLib.dataManip.psMinLM_GuessABP", 5, "using Gauss-J version");
+
+    // set new guess values (creates matrix A)
+    Beta = psVectorCopy (Beta, beta, PS_TYPE_F64);
+    Alpha = psImageCopy (Alpha, alpha, PS_TYPE_F64);
+    for (int j = 0; j < params->n; j++) {
+	if ((paramMask != NULL) && (paramMask->data.U8[j])) continue;
+        Alpha->data.F64[j][j] = alpha->data.F64[j][j] * (1.0 + lambda);
+    }
+
+    psGaussJordan (Alpha, Beta);
+# endif
+
+    // apply Beta to get new Params values
+    for (int j = 0; j < params->n; j++) {
+	if ((paramMask != NULL) && (paramMask->data.U8[j])) continue;
+        // Params->data.F32[j] = params->data.F32[j] - Beta->data.F64[j];
+	// continue;
+	// compare Beta to beta limits
+	if (beta_lim != NULL) {
+	    if (fabs(Beta->data.F64[j]) > fabs(beta_lim->data.F32[j])) {
+		Beta->data.F64[j] = (Beta->data.F64[j] > 0) ? fabs(beta_lim->data.F32[j]) : -fabs(beta_lim->data.F32[j]);
+	    }
+	}
+        Params->data.F32[j] = params->data.F32[j] - Beta->data.F64[j];
+	// compare new params to param limits
+	if (params_max != NULL) {
+	    Params->data.F32[j] = PS_MIN (Params->data.F32[j], params_max->data.F32[j]);
+	}
+	if (params_min != NULL) {
+	    Params->data.F32[j] = PS_MAX (Params->data.F32[j], params_min->data.F32[j]);
+	}
+    }
+
+# if (USE_LU_DECOMP)
+    psFree (A);
+    psFree (LUm);
+    psFree (LUv);
+# endif
+
+    return true;
+}
+
+bool psMinimizeGaussNewtonDelta_EAM (psVector *delta,
+				     const psVector *params,
+				     const psVector *paramMask,
+				     const psArray  *x,
+				     const psVector *y,
+				     const psVector *yWt,
+				     psMinimizeLMChi2Func func)
+{
+
+    // allocate internal arrays (current vs Guess)
+    psImage  *alpha  = psImageAlloc  (params->n, params->n, PS_TYPE_F64);
+    psImage  *Alpha  = psImageAlloc  (params->n, params->n, PS_TYPE_F64);
+    psVector *beta   = psVectorAlloc (params->n, PS_TYPE_F64);
+    psVector *Params = psVectorAlloc (params->n, PS_TYPE_F64);
+    psVector *dy     = NULL;
+
+    // the user provides the error or NULL.  we need to convert
+    // to appropriate weights
+    if (yWt != NULL) {
+	dy = (psVector *) yWt;
+    } else {
+	dy = psVectorAlloc (y->n, PS_TYPE_F32);
+	psVectorInit (dy, 1.0);
+//        for (int i = 0; i < dy->n; i++) {
+//            dy->data.F32[i] = 1.0;
+//        }
+    }
+
+    p_psMinLM_SetABX (alpha, beta, params, paramMask, x, y, dy, func);
+    p_psMinLM_GuessABP (Alpha, delta, Params, alpha, beta, params, paramMask, NULL, NULL, NULL, 0.0);
+
+    psFree (alpha);
+    psFree (Alpha);
+    psFree (beta);
+    psFree (Params);
+    if (yWt == NULL) {
+	psFree (dy);
+    }
+    return (true);
+}
+
+
Index: /tags/psphot_dev_02/psphot/src/psModulesUtils.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psModulesUtils.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psModulesUtils.c	(revision 5352)
@@ -0,0 +1,102 @@
+# include "psModulesUtils.h"
+
+// extract config informatin from config data or from image header, if specified
+// XXX EAM : this function should be replaced with Paul's image/header/metadata load scheme
+psF32 pmConfigLookupF32 (bool *status, psMetadata *config, psMetadata *header, char *name) {
+
+    char *source;
+    char *keyword;
+    psF32 value;
+    psMetadataItem *item;
+
+    // look for the entry in the config collection
+    item = psMetadataLookup (config, name);
+    if (item == NULL) {
+	psLogMsg ("pmConfigLookupF32", 2, "no key %s in config data, trying as header keyword", name);
+	value = psMetadataLookupF32 (status, header, name);
+	if (*status == false) {
+	    psAbort ("pmConfigLookupF32", "no key %s in header", name);
+	}
+	*status = true;
+	return (value);
+    }	
+
+    // I'm either expecting a string, with the name "HD:keyword"...
+    if (item->type == PS_DATA_STRING) {
+	source = item->data.V;
+	if (!strncasecmp (source, "HD:", 3)) {
+	    keyword = &source[3]; 
+	    value = psMetadataLookupF32 (status, header, keyword);
+	    if (*status == false) {
+		psAbort ("pmConfigLookupF32", "no key %s in config", name);
+	    }
+	    *status = true;
+	    // psFree (item);
+	    return (value);
+	}	
+    }
+
+    //  ... or a value (F32?)
+    if (item->type == PS_DATA_F32) {
+	value = item->data.F32;
+	// psFree (item);
+	return (value);
+    }
+
+    *status = false;
+    psError(PS_ERR_UNKNOWN, true, "invalid item");
+    return (0);
+}
+
+bool pmModelFitStatus (pmModel *model) {
+
+    bool status;
+
+    pmModelFitStatusFunc statusFunc = pmModelFitStatusFunc_GetFunction (model->type);
+    status = statusFunc (model);
+
+    return (status);
+}
+
+bool pmSourcePhotometry (float *fitMag, float *obsMag, pmModel *model, psImage *image, psImage *mask) {
+
+    float obsSum = 0;
+    float fitSum = 0;
+    float sky = model->params->data.F32[0];
+
+    pmModelFlux modelFluxFunc = pmModelFlux_GetFunction (model->type);
+    fitSum = modelFluxFunc (model->params);
+
+    for (int ix = 0; ix < image->numCols; ix++) {
+	for (int iy = 0; iy < image->numRows; iy++) {
+	    if (mask->data.U8[iy][ix]) continue;
+	    obsSum += image->data.F32[iy][ix] - sky;
+	}
+    }
+    if (obsSum <= 0) return false;
+    if (fitSum <= 0) return false;
+
+    *fitMag = -2.5*log10(fitSum);
+    *obsMag = -2.5*log10(obsSum);
+    return (true);
+} 
+
+// force the fitted sky to meet the source flux at outer radius 
+bool pmModelSkyOffset (pmModel *model, float x, float y, float radius) {
+
+    float flux;
+
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+    psVector *params = model->params;
+  
+    coord->data.F32[0] = x + radius;
+    coord->data.F32[1] = y;
+    flux = modelFunc (NULL, params, coord);
+    params->data.F32[0] = flux;
+
+    psFree (coord);
+
+    return true;
+}
Index: /tags/psphot_dev_02/psphot/src/psModulesUtils.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/psModulesUtils.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psModulesUtils.h	(revision 5352)
@@ -0,0 +1,21 @@
+
+# ifndef PS_MODULE_UTILS
+# define PS_MODULE_UTILS
+
+# include <strings.h>  // for strcasecmp
+# include <pslib.h>
+# include "psLibUtils.h"
+# include "pmObjects_EAM.h"
+# include "pmModelGroup.h"
+
+// psModule extra utilities
+// bool	     pmSourceFitModel_EAM(pmSource *source, pmModel *model, const bool PSF);
+bool 	     pmModelFitStatus (pmModel *model);
+int	     pmSourceDophotType (pmSource *source);
+bool	     pmSourcePhotometry (float *fitMag, float *obsMag, pmModel *model, psImage *image, psImage *mask);
+bool         pmModelSkyOffset (pmModel *model, float x, float y, float radius);
+
+// unify with paul's image/header/metadata functions
+psF32        pmConfigLookupF32 (bool *status, psMetadata *config, psMetadata *header, char *name);
+
+# endif
Index: /tags/psphot_dev_02/psphot/src/psPolynomials.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psPolynomials.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psPolynomials.c	(revision 5352)
@@ -0,0 +1,611 @@
+# include <pslib.h>
+# include "psLibUtils.h"
+
+// XXX EAM : this file defines alternate versions of the polynomial fitting
+//           functions.  these differ from psLib v.0.5.0 in the definition 
+//           of nOrder (here we use nOrder in the mathematical sense, not
+//           like IRAF.  ie, a linear function is of nOrder 1)
+//           psLib v 0.7.0 should be in sync with out defintion of nOrder
+//           If so, we can drop these functions and use the psLib versions.
+
+// write out the terms of the given 1D polynomial
+void Polynomial1DDump_EAM (psPolynomial1D *poly) {
+
+    for (int i = 0; i < poly->nX + 1; i++) {
+	fprintf (stderr, "x^%d : %g +/- %g\n", i, poly->coeff[i], poly->coeffErr[i]);
+    }
+}    
+
+static void Polynomial1DFree_EAM (psPolynomial1D *poly) {
+
+    if (poly == NULL) return;
+    psFree (poly->coeff);
+    psFree (poly->coeffErr);
+    psFree (poly->mask);
+    return;
+}
+
+// XXX EAM : use Nterm = Norder + 1 definition  
+// XXX EAM : should we provide both order and nterms in struct?
+psPolynomial1D* Polynomial1DAlloc_EAM(psPolynomialType type,
+				      psS32 nOrder)
+{
+    // PS_INT_CHECK_NON_NEGATIVE(nOrder, NULL);
+
+    psS32 i = 0;
+    psS32 nTerm = nOrder + 1;
+    psPolynomial1D* newPoly = NULL;
+
+    newPoly = (psPolynomial1D* ) psAlloc(sizeof(psPolynomial1D));
+    newPoly->type = type;
+    newPoly->nX = nOrder;
+    newPoly->coeff = (psF64 *)psAlloc(nTerm * sizeof(psF64));
+    newPoly->coeffErr = (psF64 *)psAlloc(nTerm * sizeof(psF64));
+    newPoly->mask = (psU8 *)psAlloc(nTerm * sizeof(psU8));
+    for (i = 0; i < nTerm; i++) {
+        newPoly->coeff[i] = 0.0;
+        newPoly->coeffErr[i] = 0.0;
+        newPoly->mask[i] = 0;
+    }
+    psMemSetDeallocator(newPoly, (psFreeFunc) Polynomial1DFree_EAM);
+    return(newPoly);
+}
+
+psF64 Polynomial1DEval_EAM(const psPolynomial1D* myPoly, psF64 x)
+{
+    psS32 loop_x = 0;
+    psF64 polySum = 0.0;
+    psF64 xSum = 1.0;
+
+    for (loop_x = 0; loop_x < myPoly->nX + 1; loop_x++) {
+        if (myPoly->mask[loop_x] == 0) {
+            polySum += xSum * myPoly->coeff[loop_x];
+        }
+        xSum *= x;
+    }
+
+    return(polySum);
+}
+
+psVector *Polynomial1DEvalVector_EAM(const psPolynomial1D *myPoly,
+				     const psVector *x)
+{
+    // PS_POLY_CHECK_NULL(myPoly, NULL);
+    // PS_VECTOR_CHECK_NULL(x, NULL);
+    // PS_VECTOR_CHECK_TYPE(x, PS_TYPE_F64, NULL);
+
+    psVector *tmp;
+
+    tmp = psVectorAlloc(x->n, PS_TYPE_F64);
+    for (psS32 i=0;i<x->n;i++) {
+        tmp->data.F64[i] = Polynomial1DEval_EAM(myPoly, x->data.F64[i]);
+    }
+
+    return(tmp);
+}
+
+// XXX EAM : my alternate BuildSums1D
+static psVector *BuildSums1D_EAM(psVector* sums, 
+				 psF64 x,
+				 psS32 nTerm)
+{
+    psS32 nSum = 0;
+    psF64 xSum = 0.0;
+
+    nSum = 2*nTerm;
+    if (sums == NULL) {
+        sums = psVectorAlloc(nSum, PS_TYPE_F64);
+    }
+    if (nSum > sums->n) {
+        sums = psVectorRealloc(sums, nSum);
+    }
+
+    xSum = 1.0;
+    for (int i = 0; i < nSum; i++) {
+        sums->data.F64[i] = xSum;
+        xSum *= x;
+    }
+    return (sums);
+}
+
+// XXX EAM : test version of 1d fitting
+psPolynomial1D* VectorFitPolynomial1D_EAM(psPolynomial1D* myPoly,
+					  psVector *mask,
+					  psMaskType maskValue,
+					  const psVector *y,
+					  const psVector *yErr,
+					  const psVector *x)
+{
+    // I think this is 1 dimension down
+    psImage*  	  A = NULL;
+    psVector* 	  B = NULL;
+    psVector* xSums = NULL;
+    psS32 nTerm;
+    psF64 wt;
+
+    psTrace(".psLib.dataManip.VectorFitPolynomial1DOrd", 4,
+            "---- VectorFitPolynomial1DOrd() begin ----\n");
+
+    // dump minutiae
+# ifndef PS_NO_TRACE
+# if (0)
+    // XXX EAM : change from FILE to fd breaks this code:
+    if (psTraceGetLevel (".psLib.dataManip.VectorFitPolynomial1DOrd") >= 5) {
+	FILE *f = psTraceGetDestination ();
+	fprintf (f, "VectorFitPolynomial1D()\n");
+	for (int i = 0; i < x->n; i++) {
+	    fprintf (f, "(x, y, yErr) is (%f, %f, %f)\n", x->data.F64[i], y->data.F64[i], yErr->data.F64[i]);
+	}
+    }
+# endif
+# endif
+      
+    nTerm = myPoly->nX + 1;
+    A     = psImageAlloc(nTerm, nTerm, PS_TYPE_F64);
+    B     = psVectorAlloc(nTerm, PS_TYPE_F64);
+
+    // Initialize data structures (why is this not a function!)
+    for (int i = 0; i < nTerm; i++) {
+        B->data.F64[i] = 0.0;
+        for (int j = 0; j < nTerm; j++) {
+            A->data.F64[i][j] = 0.0;
+        }
+    }
+
+    // xSums look like: 1, x, x^2, ... x^(2n+1)
+
+    // Build the B and A data structs.
+    for (int k = 0; k < x->n; k++) {
+        if ((mask != NULL) && (mask->data.U8[k] & maskValue)) continue;
+	xSums = BuildSums1D_EAM(xSums, x->data.F64[k], nTerm);
+      
+	if (yErr == NULL) {
+	    wt = 1.0;
+	} else {
+	    // this should probably by yErr^2 !!
+	    // this should filter yErr == 0 values
+	    wt = 1.0 / PS_SQR(yErr->data.F64[k]);
+	}
+	for (int i = 0; i < nTerm; i++) {
+	    B->data.F64[i] += y->data.F64[k] * xSums->data.F64[i] * wt;
+	}
+
+	// we could skip half of the array and assign at the end
+	// we must handle masked orders
+	for (int i = 0; i < nTerm; i++) {
+	    for (int j = 0; j < nTerm; j++) {
+		A->data.F64[i][j] += xSums->data.F64[i + j] * wt;
+	    }
+	}
+    }
+ 
+    // GaussJordan version
+    if (0) {
+	// does the solution in place
+	psGaussJordan (A, B);
+    
+	// the first nTerm entries in B correspond directly to the desired 
+	// polynomial coefficients.  this is only true for the 1D case
+	for (int k = 0; k < nTerm; k++) {
+	    myPoly->coeff[k] = B->data.F64[k];
+	}
+    } 
+    else 
+	// LUD version of the fit
+    {
+	psImage *ALUD = NULL;
+	psVector* outPerm = NULL;
+	psVector* coeffs = NULL;
+    
+	ALUD = psImageAlloc(nTerm, nTerm, PS_TYPE_F64);
+	ALUD = psMatrixLUD(ALUD, &outPerm, A);
+	coeffs = psMatrixLUSolve(coeffs, ALUD, B, outPerm);
+	for (int k = 0; k < nTerm; k++) {
+	    myPoly->coeff[k] = coeffs->data.F64[k];
+	}
+	psFree (ALUD);
+	psFree (outPerm);
+	psFree (coeffs);
+    }
+
+    psFree(A);
+    psFree(B);
+    psFree(xSums);
+
+    psTrace(".psLib.dataManip.VectorFitPolynomial1DOrd", 4,
+            "---- VectorFitPolynomial1DOrd() begin ----\n");
+    return (myPoly);
+}
+
+// ********************** 2D polynomial functions ******************
+
+static void Polynomial2DFree_EAM(psPolynomial2D *poly) {
+
+    if (poly == NULL) return;
+    for (int i = 0; i < poly->nX + 1; i++) {
+	psFree (poly->coeff[i]);
+	psFree (poly->coeffErr[i]);
+	psFree (poly->mask[i]);
+    }
+    psFree (poly->coeff);
+    psFree (poly->coeffErr);
+    psFree (poly->mask);
+    return;
+}
+
+// XXX EAM : use Nterm = Norder + 1 definition  
+// the user requests a polynomial of order Norder
+psPolynomial2D* Polynomial2DAlloc_EAM(psPolynomialType type,
+				      psS32 nXorder, 
+				      psS32 nYorder)
+{
+    // PS_INT_CHECK_NON_NEGATIVE(nXorder, NULL);
+    // PS_INT_CHECK_NON_NEGATIVE(nYorder, NULL);
+
+    psS32 x = 0;
+    psS32 y = 0;
+    psS32 nXterm = nXorder + 1;
+    psS32 nYterm = nYorder + 1;
+    psPolynomial2D* newPoly = NULL;
+
+    newPoly = (psPolynomial2D* ) psAlloc(sizeof(psPolynomial2D));
+    newPoly->type = type;
+    newPoly->nX = nXorder;
+    newPoly->nY = nYorder;
+
+    newPoly->coeff = (psF64 **)psAlloc(nXterm * sizeof(psF64 *));
+    newPoly->coeffErr = (psF64 **)psAlloc(nXterm * sizeof(psF64 *));
+    newPoly->mask = (psMaskType **)psAlloc(nXterm * sizeof(psMaskType *));
+    // XXX EAM : this is an error in the definition of the polynomial mask 
+    //           (should be psU8 not char)
+    for (x = 0; x < nXterm; x++) {
+        newPoly->coeff[x] = (psF64 *)psAlloc(nYterm * sizeof(psF64));
+        newPoly->coeffErr[x] = (psF64 *)psAlloc(nYterm * sizeof(psF64));
+        newPoly->mask[x] = (char *)psAlloc(nYterm * sizeof(char));
+    }
+    for (x = 0; x < nXterm; x++) {
+        for (y = 0; y < nYterm; y++) {
+            newPoly->coeff[x][y] = 0.0;
+            newPoly->coeffErr[x][y] = 0.0;
+            newPoly->mask[x][y] = 0;
+        }
+    }
+    psMemSetDeallocator(newPoly, (psFreeFunc) Polynomial2DFree_EAM);
+    return(newPoly);
+}
+
+// write out the terms of the given 2D polynomial
+void Polynomial2DDump_EAM (psPolynomial2D *poly) {
+
+    for (int i = 0; i < poly->nX + 1; i++) {
+	for (int j = 0; j < poly->nY + 1; j++) {
+	    fprintf (stderr, "x^%d y^%d : %g +/- %g\n", i, j, poly->coeff[i][j], poly->coeffErr[i][j]);
+	}
+    }
+}    
+
+psF64 Polynomial2DEval_EAM(const psPolynomial2D* myPoly,
+			   psF64 x,
+			   psF64 y)
+{
+    // PS_POLY_CHECK_NULL(myPoly, NAN);
+
+    psS32 loop_x = 0;
+    psS32 loop_y = 0;
+    psF64 polySum = 0.0;
+    psF64 xSum = 1.0;
+    psF64 ySum = 1.0;
+
+    // XXX EAM : nX is order, not nTerms
+    for (loop_x = 0; loop_x < myPoly->nX + 1; loop_x++) {
+        ySum = xSum;
+	// XXX EAM : nX is order, not nTerms
+        for (loop_y = 0; loop_y < myPoly->nY + 1; loop_y++) {
+            if (myPoly->mask[loop_x][loop_y] == 0) {
+                polySum += ySum * myPoly->coeff[loop_x][loop_y];
+            }
+            ySum *= y;
+        }
+        xSum *= x;
+    }
+
+    return(polySum);
+}
+
+// XXX EAM : this version uses myPoly->nX as Norder, not Nterms
+psVector *Polynomial2DEvalVector_EAM(const psPolynomial2D *myPoly,
+				     const psVector *x,
+				     const psVector *y)
+
+{
+    // PS_POLY_CHECK_NULL(myPoly, NULL);
+    // PS_VECTOR_CHECK_NULL(x, NULL);
+    // PS_VECTOR_CHECK_TYPE(x, PS_TYPE_F32, NULL);
+    // PS_VECTOR_CHECK_NULL(y, NULL);
+    // PS_VECTOR_CHECK_TYPE(y, PS_TYPE_F32, NULL);
+
+    psVector *tmp;
+    psS32 vecLen=x->n;
+
+    // Determine the length of the output vector to by the minimum of the x,y vectors
+    if (y->n < vecLen) {
+        vecLen = y->n;
+    }
+
+    // Create output vector to return
+    tmp = psVectorAlloc(vecLen, PS_TYPE_F32);
+
+    // Evaluate the polynomial at the specified points
+    for (psS32 i=0; i<vecLen; i++) {
+        tmp->data.F32[i] = Polynomial2DEval_EAM(myPoly, x->data.F32[i], y->data.F32[i]);
+    }
+
+    // Return output vector
+    return(tmp);
+}
+
+// XXX EAM : this version uses the F64 vectors
+psVector *Polynomial2DEvalVectorD_EAM(const psPolynomial2D *myPoly,
+				  const psVector *x,
+				  const psVector *y)
+
+{
+    // PS_POLY_CHECK_NULL(myPoly, NULL);
+    // PS_VECTOR_CHECK_NULL(x, NULL);
+    // PS_VECTOR_CHECK_TYPE(x, PS_TYPE_F64, NULL);
+    // PS_VECTOR_CHECK_NULL(y, NULL);
+    // PS_VECTOR_CHECK_TYPE(y, PS_TYPE_F64, NULL);
+
+    psVector *tmp;
+    psS32 vecLen=x->n;
+
+    // Determine the length of the output vector to by the minimum of the x,y vectors
+    if (y->n < vecLen) {
+        vecLen = y->n;
+    }
+
+    // Create output vector to return
+    tmp = psVectorAlloc(vecLen, PS_TYPE_F64);
+
+    // Evaluate the polynomial at the specified points
+    for (psS32 i=0; i<vecLen; i++) {
+        tmp->data.F64[i] = Polynomial2DEval_EAM(myPoly,x->data.F64[i],y->data.F64[i]);
+    }
+
+    // Return output vector
+    return(tmp);
+}
+
+// XXX EAM : BuildSums2D in analogy with BuildSums1D
+static psImage *BuildSums2D_EAM(psImage* sums,
+				psF64 x,      psF64 y,
+				psS32 nXterm, psS32 nYterm)
+{
+    psS32 nXsum = 0;
+    psS32 nYsum = 0;
+    psF64 xSum = 1.0;
+    psF64 ySum = 1.0;
+
+    nXsum = 2*nXterm;
+    nYsum = 2*nYterm;
+    if (sums == NULL) {
+        sums = psImageAlloc(nXsum, nYsum, PS_TYPE_F64);
+    }
+    if ((nXsum != sums->numCols) || (nYsum != sums->numRows)) {
+	psFree (sums);
+        sums = psImageAlloc(nXsum, nYsum, PS_TYPE_F64);
+    }
+
+    ySum = 1.0;
+    for (int j = 0; j < nYsum; j++) {
+	xSum = ySum;
+	for (int i = 0; i < nXsum; i++) {
+	    sums->data.F64[i][j] = xSum;
+	    xSum *= x;
+	}
+	ySum *= y;
+    }
+    return (sums);
+}
+
+// XXX EAM : test version of 2d fitting
+psPolynomial2D* VectorFitPolynomial2D_EAM(psPolynomial2D* myPoly,
+					     psVector* mask,
+					     psMaskType maskValue,
+					     const psVector* z,
+					     const psVector* zErr,
+					     const psVector* x,
+					     const psVector* y)
+{
+    // I think this is 1 dimension down
+    psImage*  	  A = NULL;
+    psVector* 	  B = NULL;
+    psImage*   Sums = NULL;
+    psF64 wt;
+    psS32 nTerm, nXterm, nYterm;
+
+    nXterm = myPoly->nX + 1;
+    nYterm = myPoly->nY + 1;
+    nTerm = nXterm * nYterm;
+
+    A = psImageAlloc(nTerm, nTerm, PS_TYPE_F64);
+    B = psVectorAlloc(nTerm, PS_TYPE_F64);
+
+    // Initialize data structures (why is this not a function!)
+    for (int i = 0; i < nTerm; i++) {
+        B->data.F64[i] = 0.0;
+        for (int j = 0; j < nTerm; j++) {
+            A->data.F64[i][j] = 0.0;
+        }
+    }
+
+    // Sums look like: 1, x, x^2, ... x^(2n+1), y, xy, x^2y, ... x^(2n+1)
+
+    // Build the B and A data structs.
+    for (int k  = 0; k < x->n; k++) {
+	if ((mask != NULL) && (mask->data.U8[k] & maskValue)) continue;
+	Sums = BuildSums2D_EAM(Sums, x->data.F64[k], y->data.F64[k], nXterm, nYterm);
+      
+	if (zErr == NULL) {
+	    wt = 1.0;
+	} else {
+	    // this should probably by zErr^2 !!
+	    // this should filter zErr == 0 values
+	    wt = 1.0 / zErr->data.F64[k];
+	}
+
+	// we could skip half of the array and assign at the end
+	// we must handle masked orders
+	for (int n = 0; n < nXterm; n++) {
+	    for (int m = 0; m < nYterm; m++) {
+		B->data.F64[n+m*nXterm] += z->data.F64[k] * Sums->data.F64[n][m] * wt;
+	    }
+	}
+
+	for (int i = 0; i < nXterm; i++) {
+	    for (int j = 0; j < nYterm; j++) {
+		for (int n = 0; n < nXterm; n++) {
+		    for (int m = 0; m < nYterm; m++) {
+			A->data.F64[i+j*nXterm][n+m*nXterm] += Sums->data.F64[i+n][j+m] * wt;
+		    }
+		}
+	    }
+	}
+    }
+ 
+    // does the solution in place
+    psGaussJordan (A, B);
+    
+    // XXX: How do we know if these routines were successful?
+    // ALUD = psMatrixLUD(ALUD, &outPerm, A);
+    // coeffs = psMatrixLUSolve(coeffs, ALUD, B, outPerm);
+
+    for (int n = 0; n < nXterm; n++) {
+	for (int m = 0; m < nYterm; m++) {
+	    myPoly->coeff[n][m] = B->data.F64[n+m*nXterm];
+	}
+    }
+
+    psFree(A);
+    psFree(B);
+    psFree(Sums);
+
+    psTrace(".psLib.dataManip.VectorFitPolynomial2DOrd", 4,
+            "---- VectorFitPolynomial2DOrd() begin ----\n");
+    return (myPoly);
+}
+
+// XXX EAM : be careful here with F32 vs F64 vectors 
+psPolynomial1D* VectorClipFitPolynomial1D_EAM(psPolynomial1D* poly,
+					      psStats *stats,
+					      psVector* mask,
+					      psMaskType maskValue,
+					      const psVector* z,
+					      const psVector* dz,
+					      const psVector* x)
+{
+    psVector *zFit   = NULL;
+    psVector *zResid = psVectorAlloc (x->n, PS_TYPE_F64);
+    float minClipSigma = stats->min;
+    float maxClipSigma = stats->max;
+    float minClipValue;
+    float maxClipValue;
+
+    // XXX EAM : use SAMPLE_MEDIAN and SAMPLE_STDEV for stats:
+    stats->options |= (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    for (int N = 0; N < stats->clipIter; N++) {
+	int Nkeep = 0;
+
+	poly   = VectorFitPolynomial1D_EAM (poly, mask, maskValue, z, dz, x);
+	zFit   = Polynomial1DEvalVector_EAM (poly, x);
+	zResid = (psVector *) psBinaryOp (zResid, (void *) z, "-", (void *) zFit);
+
+	stats  = psVectorStats (stats, zResid, NULL, mask, maskValue);
+	minClipValue = -1*fabs(minClipSigma)*stats->sampleStdev;
+	maxClipValue = +1*fabs(maxClipSigma)*stats->sampleStdev;
+
+	psTrace (".psphot.VectorClipFit", 4, "residual stats for robust fit:  %g +/- %g\n", 
+		 stats->sampleMedian, stats->sampleStdev);
+
+	// set mask if pts are not valid
+	// we are masking out any point which is out of range 
+	// recovery is not allowed with this scheme
+	for (int i = 0; i < zResid->n; i++) {
+	    if (mask->data.U8[i]) continue;
+	    if (zResid->data.F64[i] - stats->sampleMedian > maxClipValue) {
+		mask->data.U8[i] |= 0x01;
+		continue;
+	    }       
+	    if (zResid->data.F64[i] - stats->sampleMedian < minClipValue) {
+		mask->data.U8[i] |= 0x01;
+		continue;
+	    }       
+	    Nkeep ++;
+	}
+
+	psTrace (".psphot.VectorClipFit", 4, "keeping %d of %d pts for fit\n", 
+		 Nkeep, x->n);
+
+	psFree (zFit);
+    }
+    psFree (zResid);
+    return (poly);
+}
+
+
+// XXX EAM : be careful here with F32 vs F64 vectors 
+psPolynomial2D* VectorClipFitPolynomial2D_EAM(psPolynomial2D* poly,
+					      psStats *stats,
+					      psVector* mask,
+					      psMaskType maskValue,
+					      const psVector* z,
+					      const psVector* dz,
+					      const psVector* x,
+					      const psVector* y)
+{
+    psVector *zFit   = NULL;
+    psVector *zResid = psVectorAlloc (x->n, PS_TYPE_F64);
+
+    // XXX EAM : use SAMPLE_MEAN and SAMPLE_STDEV for stats:
+    stats->options |= (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    for (int N = 0; N < 3; N++) {
+	int Nkeep = 0;
+
+	poly   = VectorFitPolynomial2D_EAM (poly, mask, maskValue, z, dz, x, y);
+	zFit   = Polynomial2DEvalVectorD_EAM (poly, x, y);
+	zResid = (psVector *) psBinaryOp (zResid, (void *) z, "-", (void *) zFit);
+
+	stats  = psVectorStats (stats, zResid, NULL, mask, maskValue);
+	psTrace (".psphot.RobustFit", 4, "residual stats for robust fit:  %g +/- %g\n", 
+		 stats->sampleMedian, stats->sampleStdev);
+
+	// set mask if pts are not valid
+	// we are masking out any point which is out of range 
+	// recovery is not allowed with this scheme
+	for (int i = 0; i < zResid->n; i++) {
+	    if (mask->data.U8[i]) continue;
+	    if (fabs(zResid->data.F64[i] - stats->sampleMedian) > 3*stats->sampleStdev) {
+		mask->data.U8[i] |= 0x01;
+		continue;
+	    }       
+	    Nkeep ++;
+	}
+
+	psTrace (".psphot.RobustFit", 4, "keeping %d of %d pts for fit\n", 
+		 Nkeep, x->n);
+
+//	psTrace (".psphot.RobustFit", 4, "model pars: %f %f %f %f\n", 
+//		 poly->coeff[0][0], poly->coeff[1][0], 
+//		 poly->coeff[0][1], poly->coeff[1][1]);
+
+	psTrace (".psphot.RobustFit", 4, "model pars: %f\n", poly->coeff[0][0]);
+
+	psFree (zFit);
+    }
+    psFree (zResid);
+    return (poly);
+}
+
+// XXX EAM : VectorFitPolynomial2D and Polynomial2DEvalVector require different types (F32 vs F64)
Index: /tags/psphot_dev_02/psphot/src/psphot.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphot.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphot.c	(revision 5352)
@@ -0,0 +1,51 @@
+# include "psphot.h"
+
+int main (int argc, char **argv) {
+
+    psMetadata  *config  = NULL;
+    eamReadout  *imdata  = NULL;
+    psArray     *sources = NULL;
+    psArray     *peaks   = NULL;
+    pmPSF       *psf     = NULL;
+    psStats     *sky     = NULL;
+    pmPSFClump   psfClump;
+
+    config = psphotArguments (&argc, argv);
+
+    // load input data (image and config)
+    // create or load mask and weight images
+    // we have memory leaks here -- may be from psMetadata
+    imdata = psphotSetup (config);
+
+    // measure image stats for initial guess 
+    sky = psphotImageStats (imdata, config);
+
+    // find the peaks in the image
+    peaks = pmPeaksSigmaLimit (imdata, config, sky);
+
+    // construct sources and measure basic stats
+    sources = psphotSourceStats (imdata, config, peaks);
+
+    // classify sources based on moments, brightness
+    // XXX - this has 1 leak
+    psfClump = pmSourcePSFClump (sources, config);
+
+    // group into STAR, COSMIC, GALAXY, SATURATED
+    pmSourceRoughClass (sources, config, psfClump);
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+
+    // use bright stellar objects to measure PSF
+    psf = psphotChoosePSF (config, sources, sky);
+
+    // test PSF on all except SATURATE and DEFECT (artifacts)
+    psphotApplyPSF (imdata, config, sources, psf, sky);
+
+    // fit extended objects with galaxy models
+    psphotFitGalaxies (imdata, config, sources, sky);
+
+    // write out data in appropriate format
+    psphotOutput (imdata, config, sources, psf, sky);
+    exit (0);
+}
Index: /tags/psphot_dev_02/psphot/src/psphot.h
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphot.h	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphot.h	(revision 5352)
@@ -0,0 +1,55 @@
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include "psLibUtils.h"
+# include "pmObjects_EAM.h"
+# include "psModulesUtils.h"
+# include "pmPSF.h"
+# include "pmPSFtry.h"
+# include "pmModelGroup.h"
+
+typedef struct {
+    psImage *image;
+    psImage *mask;
+    psImage *weight;
+    psMetadata *header;
+} eamReadout;
+
+// top-level psphot functions
+psMetadata  *psphotArguments (int *argc, char **argv);
+eamReadout  *psphotSetup (psMetadata *config);
+psStats     *psphotImageStats (eamReadout *imdata, psMetadata *config);
+psArray     *psphotSourceStats (eamReadout *imdata, psMetadata *config, psArray *allpeaks);
+pmPSF       *psphotChoosePSF (psMetadata *config, psArray *sources, psStats *sky);
+bool         psphotApplyPSF (eamReadout *imdata, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky);
+bool         psphotFitGalaxies (eamReadout *imdata, psMetadata *config, psArray *sources, psStats *skyStats);
+void         psphotOutput (eamReadout *imdata, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky);
+
+bool         psphotMarkPSF (pmSource *source, float shapeNsigma, float minSN, float maxChi, float SATURATE);
+bool         psphotSubtractPSF (pmSource *source);
+int 	     psphotSortBySN (const void **a, const void **b);
+int          psphotSaveImage (psMetadata *header, psImage *image, char *filename);
+bool 	     psphotDefinePixels (pmSource *mySource, const eamReadout *imdata, psF32 x, psF32 y, psF32 Radius);
+
+psArray     *pmPeaksSigmaLimit (eamReadout *imdata, psMetadata *config, psStats *sky);
+pmModel     *pmSourceMagnitudes (pmSource *source, pmPSF *psf, float apRadius);
+pmModel     *pmSourceSelectModel (pmSource *source);
+
+// eamReadout functions
+eamReadout *eamReadoutAlloc (psImage *image, psImage *noise, psImage *mask, psMetadata *header);
+
+// output functions
+bool 	     pmSourcesWriteText (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
+bool 	     pmSourcesWriteOBJ  (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
+bool 	     pmSourcesWriteCMP  (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
+bool 	     pmSourcesWriteCMF  (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
+bool 	     pmSourcesWriteSX   (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky);
+
+bool 	     pmPeaksWriteText (psArray *sources, char *filename);
+bool 	     pmMomentsWriteText (psArray *sources, char *filename);
+bool 	     pmModelWritePSFs (psArray *sources, psMetadata *config, char *filename, pmPSF *psf);
+bool 	     pmModelWriteFLTs (psArray *sources, char *filename);
+bool 	     pmModelWriteNULLs (psArray *sources, char *filename);
+
+int  	     pmSourcesDophotType (pmSource *source);
Index: /tags/psphot_dev_02/psphot/src/psphotApplyPSF.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotApplyPSF.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotApplyPSF.c	(revision 5352)
@@ -0,0 +1,98 @@
+# include "psphot.h"
+
+// fit psf model to all objects 
+// PSFSTAR objects will be refitted
+// run this function to a specific flux limit?
+
+bool psphotApplyPSF (eamReadout *imdata, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky) 
+{ 
+    bool  status;
+    float x;
+    float y;
+    int   Nfit = 0;
+    int   Nsub = 0;
+    int   Niter = 0;
+
+    psTimerStart ("psphot");
+
+    // we may set this differently here from the value used to mark likely saturated stars
+    float SATURATION   = psMetadataLookupF32 (&status, config, "SATURATION");
+    float OUTER_RADIUS = psMetadataLookupF32 (&status, config, "SKY_OUTER_RADIUS");
+
+    float PSF_MIN_SN   	   = psMetadataLookupF32 (&status, config, "PSF_MIN_SN");
+    float PSF_MAX_CHI  	   = psMetadataLookupF32 (&status, config, "PSF_MAX_CHI");
+    float PSF_FIT_NSIGMA   = psMetadataLookupF32 (&status, config, "PSF_FIT_NSIGMA");
+    float PSF_FIT_PADDING  = psMetadataLookupF32 (&status, config, "PSF_FIT_PADDING");
+    float PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, config, "PSF_SHAPE_NSIGMA");
+
+    // set the object surface-brightness limit for fitted pixels
+    float FLUX_LIMIT  = PSF_FIT_NSIGMA * sky->sampleStdev;
+    psLogMsg ("psphot.apply_psf_model", 4, "fitting pixels with at least %f object counts\n", FLUX_LIMIT);
+
+    // this function specifies the radius at this the model hits the given flux
+    pmModelRadius modelRadius = pmModelRadius_GetFunction (psf->type);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	// XXX EAM : should we try these anyway?
+	if (source->type == PM_SOURCE_DEFECT) continue; 
+	if (source->type == PM_SOURCE_SATURATED) continue;
+
+	// use the source moments, etc to guess basic model parameters
+	pmModel *modelFLT = pmSourceModelGuess (source, psf->type); 
+
+	// set PSF parameters for this model
+	pmModel *model = pmModelFromPSF (modelFLT, psf);
+	x = model->params->data.F32[2];
+	y = model->params->data.F32[3];
+	psFree (modelFLT);
+
+	// set the fit radius based on the object flux limit and the model
+	// XXX EAM : FLUX_LIMIT should be set based on local sky model (not global median)
+	model->radius = modelRadius (model->params, FLUX_LIMIT) + PSF_FIT_PADDING;
+	if (isnan(model->radius)) {
+	  psAbort ("apply_psf_model", "error in radius");
+	}
+	
+	// check if we need to redefine the pixels
+	// XXX EAM : a better test would examine the source pixels
+	if (model->radius > OUTER_RADIUS) {
+	  // (re)-allocate image, weight, mask arrays for each peak (square of radius OUTER)
+	    psphotDefinePixels (source, imdata, x, y, model->radius);
+	}
+
+	// if (i > 66) psTraceSetLevel (".psLib.dataManip.psMinimizeLMChi2", 5);
+	    
+	// fit PSF model (set/unset the pixel mask)
+	psImageKeepCircle (source->mask, x, y, model->radius, "OR", PSPHOT_MASK_MARKED);
+	status = pmSourceFitModel (source, model, true);
+	psImageKeepCircle (source->mask, x, y, model->radius, "AND", ~PSPHOT_MASK_MARKED);
+
+	if (!status || (model->params->data.F32[1] < 0)) {
+	  psLogMsg ("psphot", 5, "PSF fit failed for %f, %f (%d iterations)\n", x, y, model->nIter);
+	  source->type = PM_SOURCE_FAIL_FIT_PSF;  // better choice?
+	  psFree (model);
+	  continue;
+	}
+	// XXX EAM : this was an attempt to correct for fit-sky biases
+	// pmModelSkyOffset (model, x, y, model->radius);
+
+	// model succeeded : tentatively keep it
+	source->modelPSF = model;
+	Niter += model[0].nIter;
+	Nfit ++;
+
+	// is it a good model?
+	psphotMarkPSF (source, PSF_SHAPE_NSIGMA, PSF_MIN_SN, PSF_MAX_CHI, SATURATION);
+	if (psphotSubtractPSF (source)) {
+	  Nsub ++;
+	}
+    }
+
+    psLogMsg ("psphot", 3, "fit PSF models: %f sec for %d objects (%d total iterations)\n", psTimerMark ("psphot"), Nfit, Niter);
+    psLogMsg ("psphot", 4, "subtracted %d PSF objects\n", Nsub);
+    return (true);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotArguments.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotArguments.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotArguments.c	(revision 5352)
@@ -0,0 +1,86 @@
+# include "psphot.h"
+
+static void usage (void) ;
+
+psMetadata *psphotArguments (int *argc, char **argv) {
+
+  int N, Nfail;
+  int mode = PS_DATA_STRING | PS_META_REPLACE;
+
+  // basic pslib options
+  fprintf (stderr, "starting... %s\n", psLibVersion());
+  psLogSetFormat ("M");
+  psLogArguments (argc, argv);
+  psTraceArguments (argc, argv);
+
+  // optional mask image - add to config
+  char *mask = NULL;
+  if ((N = psArgumentGet (*argc, argv, "-mask"))) {
+    psArgumentRemove (N, argc, argv);
+    mask = psStringCopy (argv[N]);
+    psArgumentRemove (N, argc, argv);
+  }
+
+  // optional weight image - add to config
+  char *weight = NULL;
+  if ((N = psArgumentGet (*argc, argv, "-weight"))) {
+    psArgumentRemove (N, argc, argv);
+    weight = psStringCopy (argv[N]);
+    psArgumentRemove (N, argc, argv);
+  }
+
+  // optional output residual image - add to config
+  char *resid = NULL;
+  if ((N = psArgumentGet (*argc, argv, "-resid"))) {
+    psArgumentRemove (N, argc, argv);
+    resid = psStringCopy (argv[N]);
+    psArgumentRemove (N, argc, argv);
+  }
+
+  // optional output residual image - add to config
+  char *photcode = NULL;
+  if ((N = psArgumentGet (*argc, argv, "-photcode"))) {
+    psArgumentRemove (N, argc, argv);
+    photcode = psStringCopy (argv[N]);
+    psArgumentRemove (N, argc, argv);
+  }
+
+  if (*argc != 4) usage ();
+
+  // load config information
+  psMetadata *config = psMetadataAlloc ();
+  psMetadataAdd (config, PS_LIST_HEAD, "PSF_MODEL", PS_DATA_METADATA_MULTI, "folder for psf model entries", NULL);
+  config = psMetadataConfigParse (config, &Nfail, argv[3], FALSE);
+
+  // identify input image & optional weight & mask images
+  // command-line entries override config-file entries
+  psMetadataAdd (config, PS_LIST_HEAD, "IMAGE",       mode, "", argv[1]);
+  psMetadataAdd (config, PS_LIST_HEAD, "OUTPUT_FILE", mode, "", argv[2]);
+
+  if (mask != NULL) {
+    psMetadataAdd (config, PS_LIST_HEAD, "MASK_IMAGE", mode, "", mask);
+  }
+  if (weight != NULL) {
+    psMetadataAdd (config, PS_LIST_HEAD, "WEIGHT_IMAGE", mode, "", weight);
+  }
+  if (resid != NULL) {
+    psMetadataAdd (config, PS_LIST_HEAD, "RESID_IMAGE", mode, "", resid);
+  }
+  if (photcode != NULL) {
+    psMetadataAdd (config, PS_LIST_HEAD, "PHOTCODE", mode, "", photcode);
+  } else {
+    psMetadataAdd (config, PS_LIST_HEAD, "PHOTCODE", mode, "", "NONE");
+  }    
+  return (config);
+}
+
+static void usage (void) {
+
+    fprintf (stderr, "USAGE: psphot (image) (output) (config)\n");
+    fprintf (stderr, "options: \n");
+    fprintf (stderr, "  -mask  (filename)\n");
+    fprintf (stderr, "  -weight (filename)\n");
+    fprintf (stderr, "  -resid (filename)\n");
+    fprintf (stderr, "  -photcode (photcode)\n");
+    exit (2);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotChoosePSF.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotChoosePSF.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotChoosePSF.c	(revision 5352)
@@ -0,0 +1,78 @@
+# include "psphot.h"
+
+// try PSF models and select best option
+
+pmPSF *psphotChoosePSF (psMetadata *config, psArray *sources, psStats *skystats) 
+{ 
+    bool       	    status;
+    char       	   *modelName;
+    pmPSFtry 	   *try   = NULL;
+    psArray    	   *stars = NULL;
+    psMetadataItem *item  = NULL;
+
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, config, "PSF_MAX_NSTARS");
+    if (!status) NSTARS = sources->n;
+    stars = psArrayAlloc (sources->n);
+    stars->n = 0;
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; (i < sources->n) && (stars->n < NSTARS); i++) {
+	pmSource *source = sources->data[i];
+	if (source->type != PM_SOURCE_PSFSTAR) continue;
+	psArrayAdd (stars, 200, source);
+    }
+    psTrace (".psphot.pspsf", 3, "selected candidate %d PSF objects\n", stars->n);
+
+    // get the fixed PSF fit radius
+    // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+
+    // get the list pointers for the PSF_MODEL entries
+    psMetadataItem *mdi = psMetadataLookup (config, "PSF_MODEL");
+    if (mdi == NULL) psAbort ("psphotChoosePSF", "missing PSF_MODEL selection");
+
+    psList *list = (psList *) mdi->data.list;
+    psListIterator *iter = psListIteratorAlloc (list, PS_LIST_HEAD, FALSE); 
+
+    // set up an array to store the results
+    psArray *models = psArrayAlloc (list->n);
+
+    // try each model option listed in config
+    for (int i = 0; i < models->n; i++) { 
+
+	item = psListGetAndIncrement (iter);
+	modelName = item->data.V;
+
+	models->data[i] = pmPSFtryModel (stars, modelName, RADIUS);
+	psFree (modelName);
+	psFree (item);
+    }
+    psFree (iter);
+    // psFree (list); XXX EAM - is list freed with iter?
+    psFree (stars);
+
+    // select the best of the models
+    // here we are using the clippedStdev on the metric as the indicator
+    try = models->data[0];
+    int bestN = 0;
+    float bestM = try->psf->dApResid;
+    for (int i = 1; i < models->n; i++) {
+	try = models->data[i];
+	float M = try->psf->dApResid;
+	if (M < bestM) {
+	    bestM = M;
+	    bestN = i;
+	}
+    }
+
+    // keep only the selected model:
+    try = models->data[bestN];
+    pmPSF *psf = psMemCopy(try->psf);
+    psFree (models);				 // keep only the pmPSF resulting from this analysis
+
+    modelName = pmModelGetType (psf->type);
+    psLogMsg ("psphot.pspsf", 3, "selected psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
+
+    return (psf);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotDefinePixels.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotDefinePixels.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotDefinePixels.c	(revision 5352)
@@ -0,0 +1,21 @@
+# include "psphot.h"
+
+bool psphotDefinePixels(pmSource *mySource, 
+			const eamReadout *imdata,
+			psF32 x, 
+			psF32 y,
+			psF32 Radius)
+{
+    psRegion srcRegion;
+
+    // Grab a subimage of the original image of size (2 * outerRadius).
+    srcRegion = psRegionForSquare (x, y, Radius);
+    srcRegion = psRegionForImage (imdata->image, srcRegion);
+
+    mySource->pixels = psImageSubset(imdata->image, srcRegion);
+    mySource->weight = psImageSubset(imdata->weight, srcRegion);
+    mySource->mask   = psImageSubset(imdata->mask,  srcRegion);
+
+    return(mySource);
+}
+
Index: /tags/psphot_dev_02/psphot/src/psphotFitGalaxies.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotFitGalaxies.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotFitGalaxies.c	(revision 5352)
@@ -0,0 +1,116 @@
+# include "psphot.h"
+
+bool psphotFitGalaxies (eamReadout *imdata, psMetadata *config, psArray *sources, psStats *skyStats) 
+{ 
+    bool  status, goodfit;
+    float x;
+    float y;
+    float sky;
+    int   Nfit = 0;
+    int   Nfail = 0;
+    int   Niter = 0;
+
+    float OUTER_RADIUS	   = psMetadataLookupF32 (&status, config, "SKY_OUTER_RADIUS");
+
+    float GAL_MIN_SN  	   = psMetadataLookupF32 (&status, config, "GAL_MIN_SN");
+    float GAL_FIT_NSIGMA   = psMetadataLookupF32 (&status, config, "GAL_FIT_NSIGMA");
+    float GAL_FIT_PADDING  = psMetadataLookupF32 (&status, config, "GAL_FIT_PADDING");
+    float GAL_MOMENTS_RAD  = psMetadataLookupF32 (&status, config, "GAL_MOMENTS_RADIUS");
+
+    float FLUX_LIMIT       = GAL_FIT_NSIGMA * skyStats->sampleStdev;
+
+    char         *modelName   = psMetadataLookupPtr (&status, config, "GAL_MODEL");
+    pmModelType   modelType   = pmModelSetType (modelName);
+    pmModelRadius modelRadius = pmModelRadius_GetFunction (modelType);
+
+    psTimerStart ("psphot");
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// sources which should not be fitted
+	// skip all valid stars
+	if (source->type == PM_SOURCE_PSFSTAR) continue;
+	if (source->type == PM_SOURCE_SATSTAR) continue;
+	if (source->type == PM_SOURCE_GOODSTAR) continue;
+
+	// skip all likely defects
+	if (source->type == PM_SOURCE_DEFECT) continue;
+	if (source->type == PM_SOURCE_SATURATED) continue;
+
+	// skip poorly fitted stars
+	if (source->type == PM_SOURCE_FAINTSTAR) continue;
+	if (source->type == PM_SOURCE_POOR_FIT_PSF) continue;
+
+	// XXX EAM when do we pick these up again?
+	if (source->moments->SN < GAL_MIN_SN) {
+	  source->type = PM_SOURCE_FAINT_GALAXY;  // better choice?
+	  continue;
+	}
+
+	// recalculate the source moments using the larger galaxy moments radius
+	status = pmSourceMoments (source, GAL_MOMENTS_RAD);
+	if (!status) {
+	  source->type = PM_SOURCE_DROP_GALAXY;  // better choice?
+	  continue;
+	}
+
+	// use the source moments, etc to guess basic model parameters
+	pmModel  *model  = pmSourceModelGuess (source, modelType); 
+	x = model->params->data.F32[2];
+	y = model->params->data.F32[3];
+
+	// set the fit radius based on the object flux limit and the model
+	// XXX EAM : FLUX_LIMIT should be set based on local sky model (not global median)
+	model->radius = modelRadius (model->params, FLUX_LIMIT) + GAL_FIT_PADDING;
+	if (isnan(model->radius)) psAbort ("fit_galaxies", "error in radius");
+
+	// check if we need to redefine the pixels
+	// XXX EAM : a better test would examine the source pixels
+	if (model->radius > OUTER_RADIUS) {
+	  // (re)-allocate image, weight, mask arrays for each peak (square of radius OUTER)
+	  psphotDefinePixels (source, imdata, x, y, model->radius);
+	}
+
+	// fit FLT (not PSF) model (set/unset the pixel mask)
+	psImageKeepCircle (source->mask, x, y, model->radius, "OR", PSPHOT_MASK_MARKED);
+	// XXX EAM : 0.5.0 code used _EAM version
+	// status = pmSourceFitModel (source, model, false);
+	status = pmSourceFitModel (source, model, false);
+	psImageKeepCircle (source->mask, x, y, model->radius, "AND", ~PSPHOT_MASK_MARKED);
+	if (!status) {
+	  // if the fit fails, we need to change the classification
+	  psLogMsg ("psphot", 5, "GAL fit failed for %f, %f (%d iterations, %f radius)\n", x, y, model->nIter, model->radius);
+	  source->type = PM_SOURCE_FAIL_FIT_GAL;  // better choice?
+	  source->modelFLT = model;
+	  Nfail ++;
+	  continue;
+	}
+
+	// check if model fit is acceptable
+	goodfit = pmModelFitStatus (model);
+	if (!goodfit) {
+	  // if the fit fails, we need to change the classification
+	  psLogMsg ("psphot", 5, "GAL fit poor for %f, %f (%d iterations, %f radius)\n", x, y, model->nIter, model->radius);
+	  source->type = PM_SOURCE_POOR_FIT_GAL;  // better choice?
+	  source->modelFLT = model;
+	  Nfail ++;
+	  continue;
+	}
+
+	// accept the model
+	source->type = PM_SOURCE_GALAXY;
+	source->modelFLT = model;
+	Niter += model[0].nIter;
+	Nfit++;
+
+	// subtract object, leave local sky
+	sky = model->params->data.F32[0];
+	model->params->data.F32[0] = 0;
+	pmSourceSubModel (source->pixels, source->mask, model, false);
+	model->params->data.F32[0] = sky;
+
+    }
+    psLogMsg ("psphot", 3, "fit galaxies: %f sec for %d galaxies (%d failures, %d total iterations)\n", psTimerMark ("psphot"), Nfit, Nfail, Niter);
+    return (true);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotImageStats.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotImageStats.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotImageStats.c	(revision 5352)
@@ -0,0 +1,56 @@
+# include "psphot.h"
+
+psStats *psphotImageStats (eamReadout *imdata, psMetadata *config) 
+{ 
+    psStats *stats = NULL;
+    psStats *sky   = NULL;
+
+    psImage *image = imdata->image;
+
+    // get image stats on a subset of the image (random 1e5 pts)
+    psTimerStart ("psphot");
+
+    // this section fixes a bug in the psImageStats definition:
+    // it should limit the number of elements used for the initial 
+    // pass on the stats
+    { 
+	bool     status  = false;
+	int      Npixels = image->numRows*image->numCols;
+	int      Nsubset = PS_MIN (Npixels, psMetadataLookupF32 (&status, config, "IMSTATS_NPIX"));
+	psVector *subset = psVectorAlloc (Nsubset, PS_TYPE_F32);
+	psRandom *rnd    = psRandomAlloc (PS_RANDOM_TAUS, 0);
+
+	// choose Nsubset points between 0 and Nx*Ny, convert to coords
+	Npixels = image->numRows*image->numCols;
+	for (int i = 0; i < Nsubset; i++) {
+	  double frnd = psRandomUniform (rnd);
+	  int pixel = Npixels * frnd;
+	  int ix = pixel / image->numCols;
+	  int iy = pixel % image->numCols;
+	  subset->data.F32[i] = image->data.F32[iy][ix];
+	}
+
+	// this should use ROBUST not SAMPLE median
+	// robust median is broken in pslib (0.5)
+	stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN); 
+	stats = psVectorStats (stats, subset, NULL, NULL, 0); // use a mask?
+	psFree (subset);
+	psFree (rnd);
+    }
+
+    // we store these values in mean,stdev 
+    bool status = false;
+    float NOISE = psMetadataLookupF32 (&status, config, "RDNOISE");
+    float GAIN  = psMetadataLookupF32 (&status, config, "GAIN");
+
+    // convert instrumental background to poisson stats
+    sky = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+    sky->sampleMean   = stats->sampleMedian;
+    sky->sampleStdev  = sqrt(sky->sampleMean/GAIN + PS_SQR(NOISE));
+
+    psLogMsg ("psphot", 4, "stats: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot", 3, "background: %f +/- %f\n", sky->sampleMean, sky->sampleStdev);
+
+    psFree (stats);
+    return (sky);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotMagnitudes.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotMagnitudes.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotMagnitudes.c	(revision 5352)
@@ -0,0 +1,79 @@
+# include "psphot.h"
+
+// XXX EAM : this aperture correction business is invalid (& wrong) for galaxies
+pmModel *pmSourceMagnitudes (pmSource *source, pmPSF *psf, float apRadius) {
+
+    int status;
+    float x, y;
+    float sky, rflux, apMag, fitMag;
+
+    // use the correct model (PSF vs FLT)
+    pmModel *model = pmSourceSelectModel (source);
+    if (model == NULL) return NULL;
+
+    x = source->peak->x;
+    y = source->peak->y;
+
+    // we have already (psphotApplyPSF) defined pixels at least OUTER_RADIUS from source
+    // we need to mask pixels to measure the aperture magnitude
+    // set aperture mask circle of PSF_FIT_RADIUS 
+    psImageKeepCircle (source->mask, x, y, apRadius, "OR", PSPHOT_MASK_MARKED);
+
+    // save local sky for later
+    sky = model->params->data.F32[0];
+
+    // replace source flux
+    // XXX EAM : need to add 'sky' boolean option here 
+    model->params->data.F32[0] = 0;
+    pmSourceAddModel (source->pixels, source->mask, model, false);
+    model->params->data.F32[0] = sky;
+
+    // measure object photometry
+    status = pmSourcePhotometry (&fitMag, &apMag, model, source->pixels, source->mask);
+
+    // correct both apMag and fitMag to same system, consistent with infinite flux star in aperture RADIUS
+    rflux   = pow (10.0, 0.4*fitMag);
+    source->apMag  = apMag  - rflux * psf->skyBias * (M_PI * PS_SQR(apRadius));
+    source->fitMag = fitMag + psf->ApResid;
+
+    // subtract object, leave local sky
+    // XXX EAM : need to add 'sky' boolean option here 
+    model->params->data.F32[0] = 0;
+    pmSourceSubModel (source->pixels, source->mask, model, false);
+    model->params->data.F32[0] = sky;
+
+    // unmask aperture
+    psImageKeepCircle (source->mask, x, y, apRadius, "AND", ~PSPHOT_MASK_MARKED);
+    return model;
+}
+
+pmModel *pmSourceSelectModel (pmSource *source) {
+
+    pmModel *model;
+
+    switch (source->type) {
+    
+	// use PSF model with stars
+      case PM_SOURCE_PSFSTAR:
+      case PM_SOURCE_SATSTAR:
+      case PM_SOURCE_GOODSTAR:
+	model = source->modelPSF;
+	break;
+
+	// use FLT model with galaxies
+      case PM_SOURCE_GALAXY:
+	model = source->modelFLT;
+	break;
+	    
+	// skip defects and poorly fitted stars or galaxies
+      case PM_SOURCE_DEFECT:
+      case PM_SOURCE_SATURATED:
+      case PM_SOURCE_FAINTSTAR:
+      case PM_SOURCE_POOR_FIT_PSF:
+      case PM_SOURCE_POOR_FIT_GAL:
+      case PM_SOURCE_FAIL_FIT_GAL:
+      default:
+	model = NULL;
+    }
+    return (model);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotMarkPSF.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotMarkPSF.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotMarkPSF.c	(revision 5352)
@@ -0,0 +1,96 @@
+# include "psphot.h"
+
+// identify objects consistent with PSF shape/magnitude distribution
+// we expect dparams[4],dparams[5] to have a scatter of:
+// sigma_x / (S/N) * sqrt(2)
+// 1 / (params[4],params[5])*(S/N)
+
+// sigma_x : 1 / SX
+// dsx : 1 / (SX * SN)
+// dsx_o = hypot (1/(SX*SN), MIN_DSX)
+
+// any objects which is consistent with the PSF should have 
+// abs(dparams[5]) < N * dsxLine(mag) & abs(dparams[6]) < N * dsyLine(mag)
+// this includes a minimum buffer (DS) for the brighter objects
+
+// saturated stars should fall outside (but are already IDed)
+// galaxies should be larger, cosmic rays smaller, but need to test?
+// we also reject objects with S/N too low or ChiSquare to high
+
+// any object which meets the criterion is marked as PM_SOURCE_BRIGHTSTAR 
+
+// floor for DS value 
+// XXX EAM : add to configuration?
+# define MIN_DS 0.01
+
+bool psphotMarkPSF (pmSource *source, float shapeNsigma, float minSN, float maxChi, float SATURATE)
+{ 
+    int keep;
+    float dSX, dSY, SX, SY, SN;
+    float nSx, nSy, Chi;
+
+    if (source->modelPSF == NULL) return (false);
+
+    // if object has fitted peak above saturation, label as SATSTAR
+    // remember: fit does not use saturated pixels (masked)
+    if (source->modelPSF->params->data.F32[1] >= SATURATE) {
+	if (source->type == PM_SOURCE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->type = PM_SOURCE_SATSTAR;
+	return (true);
+    } 
+    if (source->type == PM_SOURCE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked GOODSTAR (fitted peak below saturation)\n");
+	source->type = PM_SOURCE_GOODSTAR;
+    }
+
+    SN  = source->modelPSF->params->data.F32[1]/source->modelPSF->dparams->data.F32[1];
+    SX  = source->modelPSF->params->data.F32[4];
+    SY  = source->modelPSF->params->data.F32[5];
+    dSX = source->modelPSF->dparams->data.F32[4];
+    dSY = source->modelPSF->dparams->data.F32[5];
+    Chi = source->modelPSF->chisq / source->modelPSF->nDOF;
+
+    // swing of sigma_x,y in sigmas
+    nSx = dSX / hypot (MIN_DS, 1 / (SX * SN));
+    nSy = dSY / hypot (MIN_DS, 1 / (SY * SN));
+
+    // assign PM_SOURCE_GOODSTAR to bright objects within PSF region of dparams[]
+    keep = TRUE;
+    keep &= (fabs(nSx) < shapeNsigma);
+    keep &= (fabs(nSy) < shapeNsigma);
+    keep &= (SN > minSN);
+    keep &= (Chi < maxChi);
+    if (keep) {
+	if (source->type == PM_SOURCE_PSFSTAR) return (true);
+	source->type = PM_SOURCE_GOODSTAR;
+	return (true);
+    }
+    
+    if (source->type == PM_SOURCE_PSFSTAR) {
+	psLogMsg ("psphot", 5, "PSFSTAR demoted based on fit quality\n");
+    }
+
+    // object appears to be small, suspected defect
+    if ((nSx <= -shapeNsigma) || (nSy <= -shapeNsigma)) {
+	source->type = PM_SOURCE_DEFECT;
+	return (false);
+    }
+
+    // object appears to be large, suspected galaxy
+    if ((nSx >= shapeNsigma) || (nSy >= shapeNsigma)) {
+	source->type = PM_SOURCE_GALAXY;
+	return (false);
+    }
+
+    // object appears to be extremely faint: what is this?
+    if (SN < minSN) {
+	source->type = PM_SOURCE_FAINTSTAR;
+	return (false);
+    }
+
+    // these are pooly fitted, probable stars near other stars?
+    source->type = PM_SOURCE_POOR_FIT_PSF;
+    return (false);
+}	
Index: /tags/psphot_dev_02/psphot/src/psphotOutput.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotOutput.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotOutput.c	(revision 5352)
@@ -0,0 +1,617 @@
+# include "psphot.h"
+
+// output functions: we have several fixed modes (sx, obj, cmp)
+void psphotOutput (eamReadout *imdata, psMetadata *config, psArray *sources, pmPSF *psf, psStats *sky) {
+
+    bool status;
+
+    char *outputMode = psMetadataLookupPtr (&status, config, "OUTPUT_MODE");
+    char *outputFile = psMetadataLookupPtr (&status, config, "OUTPUT_FILE");
+    char *residImage = psMetadataLookupPtr (&status, config, "RESID_IMAGE");
+
+    if (residImage != NULL) psphotSaveImage (imdata->header, imdata->image, residImage);
+
+    if (outputFile == NULL) {
+	psLogMsg ("output", 3, "no data output file selected");
+	return;
+    }
+    if (outputMode == NULL) {
+	psLogMsg ("output", 3, "no data output mode selected");
+	return;
+    }
+    if (!strcasecmp (outputMode, "TEXT")) {
+	pmSourcesWriteText (imdata, config, outputFile, sources, psf, sky);
+	return;
+    }
+
+    if (!strcasecmp (outputMode, "OBJ")) {
+	pmSourcesWriteOBJ (imdata, config, outputFile, sources, psf, sky);
+	return;
+    }
+  
+    if (!strcasecmp (outputMode, "SX")) {
+	pmSourcesWriteSX (imdata, config, outputFile, sources, psf, sky);
+	return;
+    }
+  
+    if (!strcasecmp (outputMode, "CMP")) {
+	pmSourcesWriteCMP (imdata, config, outputFile, sources, psf, sky);
+	return;
+    }
+  
+    if (!strcasecmp (outputMode, "CMF")) {
+	pmSourcesWriteCMF (imdata, config, outputFile, sources, psf, sky);
+	return;
+    }
+    psAbort ("psphot", "unknown output mode %s", outputMode);
+}
+
+bool pmSourcesWriteText (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *sky) {
+
+    char *name = (char *) psAlloc (strlen(filename) + 10);
+
+    sprintf (name, "%s.psf.dat", filename);
+    pmModelWritePSFs (sources, config, name, psf);
+
+    sprintf (name, "%s.flt.dat", filename);
+    pmModelWriteFLTs (sources, name);
+
+    sprintf (name, "%s.nul.dat", filename);
+    pmModelWriteNULLs (sources, name);
+
+    sprintf (name, "%s.mnt.dat", filename);
+    pmMomentsWriteText (sources, name);
+
+    psFree (name);
+    return true;
+}
+
+// dophot-style output list with fixed line width
+bool pmSourcesWriteOBJ (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
+
+    int type;
+    pmModel *model;
+    psF32 *PAR, *dPAR;
+    float dmag, apResid;
+    bool status;
+
+    psLine *line = psLineAlloc (104);  // 104 is dophot-defined line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("WriteSourceOBJ", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+
+    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+
+    // write sources with models 
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+
+	model = pmSourceMagnitudes (source, psf, RADIUS);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	dmag = dPAR[1] / PAR[1];
+	type = pmSourceDophotType (source);
+	apResid = source->apMag - source->fitMag;
+
+	psLineInit (line);
+	psLineAdd (line, "%3d",   type);
+	psLineAdd (line, "%8.2f", PAR[2]);
+	psLineAdd (line, "%8.2f", PAR[3]);
+	psLineAdd (line, "%8.3f", source->fitMag);
+	psLineAdd (line, "%6.3f", dmag);
+	psLineAdd (line, "%9.2f", PAR[0]);
+	psLineAdd (line, "%9.3f", PAR[4]);
+	psLineAdd (line, "%9.3f", PAR[5]);
+	psLineAdd (line, "%7.2f", PAR[6]);
+	psLineAdd (line, "%8.3f", 32.0);
+	psLineAdd (line, "%8.3f", source->apMag);
+	psLineAdd (line, "%8.2f\n", apResid);
+	fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    return true;
+}
+
+// elixir-mode / sextractor-style output list with fixed line width
+bool pmSourcesWriteSX (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
+
+    pmModel *model;
+    psF32 *PAR, *dPAR;
+    float dmag;
+    bool status;
+
+    psLine *line = psLineAlloc (110);  // 110 is sextractor line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("WriteSourceSX", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+
+    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+
+    // write sources with models 
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+
+	model = pmSourceMagnitudes (source, psf, RADIUS);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	// pmSourceSextractType (source, &type, &flags); XXX EAM : implement me...
+
+	dmag = dPAR[1] / PAR[1];
+
+	psLineInit (line);
+	psLineAdd (line, "%5.2f", 0.0); // should be type
+	psLineAdd (line, "%11.3f", PAR[2]);
+	psLineAdd (line, "%11.3f", PAR[3]);
+	psLineAdd (line, "%9.4f", source->fitMag);
+	psLineAdd (line, "%9.4f", dmag);
+	psLineAdd (line, "%13.4f", PAR[0]);
+	psLineAdd (line, "%9.2f", 0.0); // should be FWHMx
+	psLineAdd (line, "%9.2f", 0.0); // should be FWHMy
+	psLineAdd (line, "%6.1f", 0.0); // should be Theta
+	psLineAdd (line, "%9.4f", 32.0); // should be MAG_ISO
+	psLineAdd (line, "%9.4f", source->apMag);
+	psLineAdd (line, "%4d\n", 0); // should be flags
+	fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    return true;
+}
+
+// elixir-style pseudo FITS table (header + ascii list)
+bool pmSourcesWriteCMP (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
+
+    int i, type;
+    pmModel *model;
+    psMetadataItem *mdi;
+    psF32 *PAR, *dPAR;
+    float dmag, apResid, lsky;
+    bool status;
+
+    // find config information for output header
+    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+    float ZERO_POINT = psMetadataLookupF32 (&status, config, "ZERO_POINT");
+    char *PHOTCODE = psMetadataLookupPtr (&status, config, "PHOTCODE");
+
+    // write necessary information to output header
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "NSTARS",   PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS",     sources->n);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "ZERO_PT",  PS_DATA_F32 | PS_META_REPLACE, "zero point",          ZERO_POINT);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "dAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skyBias);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "PHOTCODE", PS_DATA_STRING | PS_META_REPLACE, "photometry code",     PHOTCODE);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FWHM_X",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM X",          0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FWHM_Y",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Y",          0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "ANGLE",    PS_DATA_F32 | PS_META_REPLACE, "PSF ANGLE",           0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+
+    // create file, write-out header
+    unlink (filename);
+    psFits *fits = psFitsAlloc (filename);
+
+    // set NAXIS to 0 : CFITSIO requires isolated header to have NAXIS = 0
+    mdi = psMetadataLookup (imdata->header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    // psMetadataAdd (imdata->header, PS_LIST_HEAD, "NAXIS", PS_DATA_S32 | PS_META_REPLACE, "", 0);
+    psFitsWriteHeader (imdata->header, fits);
+    psFree (fits);
+
+    // re-open, add data to end of file
+    FILE *f = fopen (filename, "a+");
+    if (f == NULL) {
+	psLogMsg ("WriteSourceOBJ", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+    fseek (f, 0, SEEK_END);
+
+    psLine *line = psLineAlloc (67);  // 66 is imclean-defined line length
+
+    // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+
+	model = pmSourceMagnitudes (source, psf, RADIUS);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	dmag = dPAR[1] / PAR[1];
+	type = pmSourceDophotType (source);
+	apResid = source->apMag - source->fitMag;
+	lsky = (PAR[0] < 1.0) ? 0.0 : log10(PAR[0]);
+
+	psLineInit (line);
+	psLineAdd (line, "%6.1f ", PAR[2]);
+	psLineAdd (line, "%6.1f ", PAR[3]);
+	psLineAdd (line, "%6.3f ", PS_MIN (32.0, source->fitMag + ZERO_POINT));
+	psLineAdd (line, "%03d ", (int)(1000*dmag));
+	psLineAdd (line, "%2d ",   type);
+	psLineAdd (line, "%3.1f ", lsky);
+	psLineAdd (line, "%6.3f ", 32.0);    // should be 'Mgal
+	psLineAdd (line, "%6.3f ", PS_MIN (32.0, source->apMag + ZERO_POINT)); 
+	psLineAdd (line, "%6.2f ", PAR[4]);  // should be 'FHWM x'
+	psLineAdd (line, "%6.2f ", PAR[5]);  // should be 'FHWM y'
+	psLineAdd (line, "%5.1f\n", 0);   // should be theta
+	fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    return true;
+}
+
+// elixir-style FITS table output (header + table in 1st extension)
+// this format consists of a header derived from the image header
+// followed by a zero-size matrix, followed by the table data
+bool pmSourcesWriteCMF (eamReadout *imdata, psMetadata *config, char *filename, psArray *sources, pmPSF *psf, psStats *skyStats) {
+
+    psArray *table;
+    psMetadataItem *mdi;
+    psMetadata *row;
+    psMetadata *theader;
+    int i, type;
+    pmModel *model;
+    psF32 *PAR, *dPAR;
+    float dmag, lsky;
+    bool status;
+
+    // find config information for output header
+    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+    float ZERO_POINT = psMetadataLookupF32 (&status, config, "ZERO_POINT");
+    char *PHOTCODE = psMetadataLookupPtr (&status, config, "PHOTCODE");
+
+    // write necessary information to output header
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "NSTARS",   PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS",     sources->n);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "ZERO_PT",  PS_DATA_F32 | PS_META_REPLACE, "zero point",          ZERO_POINT);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "dAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skyBias);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "PHOTCODE", PS_DATA_STRING | PS_META_REPLACE, "photometry code",     PHOTCODE);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FWHM_X",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM X",          0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FWHM_Y",   PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Y",          0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "ANGLE",    PS_DATA_F32 | PS_META_REPLACE, "PSF ANGLE",           0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (imdata->header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+
+    // set NAXIS to 0
+    fprintf (stderr, "setting naxis\n"); 
+    mdi = psMetadataLookup (imdata->header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    table = psArrayAlloc (sources->n);
+    sources->n = 0;
+
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+
+	model = pmSourceMagnitudes (source, psf, RADIUS);
+	if (model == NULL) continue;
+
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+
+	dmag = dPAR[1] / PAR[1];
+	type = pmSourceDophotType (source);
+	lsky = (PAR[0] < 1.0) ? 0.0 : log10(PAR[0]);
+
+	row = psMetadataAlloc ();
+	psMetadataAdd (row, PS_LIST_HEAD, "X_PIX",   PS_DATA_F32, "", PAR[2]);
+	psMetadataAdd (row, PS_LIST_HEAD, "Y_PIX",   PS_DATA_F32, "", PAR[3]);
+	psMetadataAdd (row, PS_LIST_HEAD, "MAG_RAW", PS_DATA_F32, "", source->fitMag + ZERO_POINT);
+	psMetadataAdd (row, PS_LIST_HEAD, "MAG_ERR", PS_DATA_F32, "", (int)(1000*dmag));
+	psMetadataAdd (row, PS_LIST_HEAD, "MAG_GAL", PS_DATA_F32, "", type);
+	psMetadataAdd (row, PS_LIST_HEAD, "MAG_AP",  PS_DATA_F32, "", lsky);
+	psMetadataAdd (row, PS_LIST_HEAD, "LOG_SKY", PS_DATA_F32, "", 32.0);    // should be 'Mgal'
+	psMetadataAdd (row, PS_LIST_HEAD, "FWHM_X",  PS_DATA_F32, "", source->apMag + ZERO_POINT); 
+	psMetadataAdd (row, PS_LIST_HEAD, "FWHM_Y",  PS_DATA_F32, "", PAR[4]);  // should be 'FHWM x'
+	psMetadataAdd (row, PS_LIST_HEAD, "THETA",   PS_DATA_F32, "", PAR[5]);  // should be 'FHWM y'
+	psMetadataAdd (row, PS_LIST_HEAD, "DOPHOT",  PS_TYPE_S8,  "", 0);   // should be theta
+	// psMetadataAdd (header, PS_LIST_HEAD, "DUMMY",   PS_DATA_STRING, "", NULL);
+    
+	psArrayAdd (table, 100, row);
+	psFree (row);
+    }
+
+    // psAbort ("psphotOutput", "FITS table output not finished");
+
+    // psImage *image = psImageAlloc (1, 1, PS_TYPE_F32);
+    // XXX missing: theader = psFitsHeaderFromTable (NULL, table, "SMPFILE");
+    // psFitsWriteImage (fits, imdata->header, image, 0);
+
+    theader = psMetadataAlloc ();
+    psMetadataAdd (theader, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING, "extension name", "SMPFILE");
+
+    unlink (filename);
+    psFits *fits = psFitsAlloc (filename);
+    psFitsWriteHeader (imdata->header, fits);
+    psFitsWriteTable (fits, theader, table);
+    psFree (fits);
+
+    return true;
+}
+
+/***** Text Output Methods *****/
+
+// write the peaks to an output file
+bool pmPeaksWriteText (psArray *sources, char *filename) {
+
+    int i;
+    FILE *f;
+    pmPeak *peak;
+	
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("pmPeaksWriteText", 3, "can't open output file for peaks%s\n", filename);
+	return false;
+    }
+
+    for (i = 0; i < sources->n; i++) {
+	peak = (pmPeak *) sources->data[i];
+	if (peak == NULL) continue;
+	fprintf (f, "%5d %5d  %7.1f\n", 
+		 peak->x, peak->y, peak->counts); 
+    }
+    fclose (f);
+    return true;
+}
+
+// write the moments to an output file 
+bool pmMomentsWriteText (psArray *sources, char *filename) {
+
+    int i;
+    FILE *f;
+    pmSource *source;
+	
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("pmMomentsWriteText", 3, "can't open output file for moments%s\n", filename);
+	return false;
+    }
+
+    for (i = 0; i < sources->n; i++) {
+	source = (pmSource *) sources->data[i];
+	if (source->moments == NULL) continue;
+	fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %8.1f %7.1f %7.1f %7.1f  %4d %2d\n", 
+		 source->peak->x, source->peak->y, source->peak->counts, 
+		 source->moments->x, source->moments->y, 
+		 source->moments->Sx, source->moments->Sy, 
+		 source->moments->Sum, source->moments->Peak, 
+		 source->moments->Sky, source->moments->SN, 
+		 source->moments->nPixels, source->type); 
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmModelWritePSFs (psArray *sources, psMetadata *config, char *filename, pmPSF *psf) {
+
+    double dP, dmag;
+    int i, j;
+    FILE *f;
+    psF32 *PAR, *dPAR;
+    pmModel  *model;
+    bool status;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("pmModelWritePSFs", 3, "can't open output file for moments%s\n", filename);
+	return false;
+    }
+
+    float RADIUS = psMetadataLookupF32 (&status, config, "PSF_FIT_RADIUS");
+
+   // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+
+	// valid source types for this function
+	if (source->type == PM_SOURCE_SATSTAR) goto valid;
+	if (source->type == PM_SOURCE_PSFSTAR) goto valid;
+	if (source->type == PM_SOURCE_GOODSTAR) goto valid;
+	continue;
+
+    valid:
+	model = pmSourceMagnitudes (source, psf, RADIUS);
+	if (model == NULL) continue;
+
+	PAR  = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+	
+	dP = 0;
+	dP += PS_SQR(dPAR[2]);
+	dP += PS_SQR(dPAR[3]);
+	dP = sqrt (dP);
+	
+	dmag = dPAR[1] / PAR[1];
+
+	fprintf (f, "%7.1f %7.1f  %5.1f %7.4f  %7.4f %7.4f  ", 
+		 PAR[2], PAR[3], PAR[0], source->fitMag, dmag, dP);
+
+	for (j = 0; j < model->params->n - 4; j++) {
+	    fprintf (f, "%9.6f ", PAR[j+4]);
+	}
+	fprintf (f, " : ");
+	for (j = 0; j < model->params->n - 4; j++) {
+	    fprintf (f, "%9.6f ", dPAR[j+4]);
+	}
+	fprintf (f, ": %2d %7.3f %7.3f %7.2f %4d %2d\n", 
+		 source[0].type, 
+		 log10(model[0].chisq/model[0].nDOF), 
+		 source[0].moments->SN, 
+		 model[0].radius,
+		 model[0].nDOF, 
+		 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmModelWriteFLTs (psArray *sources, char *filename) {
+
+    double dP;
+    int i, j;
+    FILE *f;
+    psVector *params;
+    psVector *dparams;
+    pmModel  *model;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("pmModelWriteFLTs", 3, "can't open output file for moments%s\n", filename);
+	return false;
+    }
+
+    // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	model = (pmModel  *) source->modelFLT;
+	if (model == NULL) continue;
+	if (source->type == PM_SOURCE_GALAXY) goto valid;
+	continue;
+
+    valid:
+	params = model->params;
+	dparams = model->dparams;
+
+	// XXX these are hardwired for SGAUSS : this should be pushed into the
+	// model functions as an abstract function 
+	dP = 0;
+	dP += PS_SQR(dparams[0].data.F32[4] / params[0].data.F32[4]);
+	dP += PS_SQR(dparams[0].data.F32[5] / params[0].data.F32[5]);
+	dP += PS_SQR(dparams[0].data.F32[7] / params[0].data.F32[7]);
+	dP = sqrt (dP);
+
+	fprintf (f, "%7.1f %7.1f  %5.1f %7.3f  %7.4f %7.4f  ", 
+		 params[0].data.F32[2], 
+		 params[0].data.F32[3], 
+		 params[0].data.F32[0], 
+		 -2.5*log10(params[0].data.F32[1]), 
+		 (dparams[0].data.F32[1]/params[0].data.F32[1]),
+		 dP);
+
+	for (j = 4; j < model->params->n; j++) {
+	    fprintf (f, "%9.6f ", params[0].data.F32[j]);
+	}
+	fprintf (f, " : ");
+	for (j = 4; j < model->params->n; j++) {
+	    fprintf (f, "%9.6f ", dparams[0].data.F32[j]);
+	}
+	fprintf (f, ": %2d %7.3f %7.3f %7.2f %4d %2d\n", 
+		 source[0].type, log10(model[0].chisq/model[0].nDOF), 
+		 source[0].moments->SN, 
+		 model[0].radius, 
+		 model[0].nDOF, 
+		 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmModelWriteNULLs (psArray *sources, char *filename) 
+{
+
+    int i;
+    FILE *f;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("DumpObjects", 3, "can't open output file for moments%s\n", filename);
+	return false;
+    }
+
+    pmMoments *empty = pmMomentsAlloc ();
+
+    // write sources with models first 
+    for (i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+
+	// skip these sources (in PSF or FLT)
+	if (source->type == PM_SOURCE_GALAXY) continue;
+	if (source->type == PM_SOURCE_PSFSTAR) continue;
+	if (source->type == PM_SOURCE_SATSTAR) continue;
+	if (source->type == PM_SOURCE_GOODSTAR) continue;
+
+	if (source->moments == NULL) {
+	  fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %8.1f %7.1f %7.1f %7.1f  %4d %2d\n", 
+		   source->peak->x, source->peak->y, source->peak->counts, 
+		   empty->x, empty->y, 
+		   empty->Sx, empty->Sy, 
+		   empty->Sum, empty->Peak, 
+		   empty->Sky, empty->SN, 
+		   empty->nPixels, source->type); 
+	} else {
+	  fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %8.1f %7.1f %7.1f %7.1f  %4d %2d\n", 
+		   source->peak->x, source->peak->y, source->peak->counts, 
+		   source->moments->x, source->moments->y, 
+		   source->moments->Sx, source->moments->Sy, 
+		   source->moments->Sum, source->moments->Peak, 
+		   source->moments->Sky, source->moments->SN, 
+		   source->moments->nPixels, source->type); 
+	}
+    }
+    fclose (f);
+    return true;
+}
+
+// translations between psphot object types and dophot object types
+int pmSourceDophotType (pmSource *source) {
+
+    switch (source->type) {
+
+      case PM_SOURCE_DEFECT:
+      case PM_SOURCE_SATURATED:
+	return (8);
+
+      case PM_SOURCE_SATSTAR:
+	return (10);
+
+      case PM_SOURCE_PSFSTAR:
+      case PM_SOURCE_GOODSTAR:
+	return (1);
+
+      case PM_SOURCE_POOR_FIT_PSF:
+	return (7);
+
+      case PM_SOURCE_FAIL_FIT_PSF:
+      case PM_SOURCE_FAINTSTAR:
+	return (4);
+
+      case PM_SOURCE_GALAXY:
+      case PM_SOURCE_FAINT_GALAXY:
+      case PM_SOURCE_DROP_GALAXY:
+      case PM_SOURCE_FAIL_FIT_GAL:
+      case PM_SOURCE_POOR_FIT_GAL:
+	return (2);
+
+      case PM_SOURCE_OTHER:
+      default:
+	return (0);
+    }
+    return (0);
+}
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    unlink (filename);
+    psFits *fits = psFitsAlloc (filename);
+    psFitsWriteImage (fits, header, image, 0);
+    psFree (fits);
+    return (TRUE);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotSetup.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotSetup.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotSetup.c	(revision 5352)
@@ -0,0 +1,98 @@
+# include "psphot.h"
+
+// load the image
+// load or construct the mask
+//   apply X/Y border info
+//   apply the SATURATE mask?
+// load or construct the weight 
+
+eamReadout *psphotSetup (psMetadata *config) 
+{
+    psMetadata *header = NULL;
+    psImage *image = NULL;
+    psImage *weight = NULL;
+    psImage *mask = NULL;
+    psRegion region = {0,0,0,0};	// a region representing the entire array
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // setup header template, specify COMMENT and HISTORY as MULTI
+    // XXX EAM - shouldn't this be part of the basic FITS concept?
+    header = psMetadataAlloc ();
+    psMetadataAdd (header, PS_LIST_HEAD, "COMMENT", PS_DATA_METADATA_MULTI, "folder for comment", NULL);
+    psMetadataAdd (header, PS_LIST_HEAD, "HISTORY", PS_DATA_METADATA_MULTI, "folder for history", NULL);
+
+    // read header and image data from INPUT 
+    char *input = psMetadataLookupPtr (&status, config, "IMAGE");
+    psFits *file = psFitsAlloc (input);
+    header = psFitsReadHeader (header, file);
+    psImage *tmpimage = psFitsReadImage (NULL, file, region, 0);
+    image = psImageCopy (NULL, tmpimage, PS_TYPE_F32);
+    psFree (file);
+    
+    // psFree (input);
+    // XXX EAM - does the current API expect to get a copy or a view?
+
+    // grab these values from the approrpiate location (image header if necessary)
+    float RDNOISE  = pmConfigLookupF32 (&status, config, header, "RDNOISE");
+    float GAIN     = pmConfigLookupF32 (&status, config, header, "GAIN");
+    psMetadataAdd (config, PS_LIST_TAIL, "RDNOISE", PS_DATA_F32 | PS_META_REPLACE, "read noise value used", RDNOISE);
+    psMetadataAdd (config, PS_LIST_TAIL, "GAIN",    PS_DATA_F32 | PS_META_REPLACE, "gain value used", GAIN);
+
+    // load the weight image if it is supplied, otherwise build from input
+    // by 'weight' we really mean variance image
+    char *weightName = psMetadataLookupPtr (&status, config, "WEIGHT");
+    if (status == true) {
+	file = psFitsAlloc (weightName);
+	weight = psFitsReadImage  (NULL, file, region, 0);
+	psFree (file);
+	// psFree (weightName); XXX - see psFree (input)
+    } else {
+	// build the weight image from the input image, RDNOISE, and GAIN
+	weight = psImageAlloc (image->numCols, image->numRows, PS_TYPE_F32);
+	for (int iy = 0; iy < image->numRows; iy++) {
+	    for (int ix = 0; ix < image->numCols; ix++) {
+		weight->data.F32[iy][ix] = image->data.F32[iy][ix] / GAIN + PS_SQR(RDNOISE/GAIN);
+	    }
+	}
+    }
+
+    // load the mask if specified, otherwise construct 
+    char *maskName = psMetadataLookupPtr (&status, config, "MASK");
+    if (status == true) {
+	// XXX EAM require / convert mask to psU8?
+	file = psFitsAlloc (maskName);
+	mask  = psFitsReadImage  (NULL, file, region, 0);
+	psFree (file);
+	// psFree (maskName); XXX - see psFree (input)
+    } else {
+	mask = psImageAlloc (image->numCols, image->numRows, PS_TYPE_U8);
+    }
+
+    // mask the excluded outer pixels
+    float XMIN  = psMetadataLookupF32 (&status, config, "XMIN");
+    float XMAX  = psMetadataLookupF32 (&status, config, "XMAX");
+    float YMIN  = psMetadataLookupF32 (&status, config, "YMIN");
+    float YMAX  = psMetadataLookupF32 (&status, config, "YMAX");
+    psRegion keep = psRegionSet (XMIN, XMAX, YMIN, YMAX);
+    keep = psRegionForImage (image, keep);
+    psImageKeepRegion (mask, keep, "OR", PSPHOT_MASK_INVALID);
+
+    // mask the saturated pixels
+    // XXX EAM does the mask need to grow?
+    float SATURATION = psMetadataLookupF32 (&status, config, "SATURATION");
+    for (int i = 0; i < image->numRows; i++) {
+	for (int j = 0; j < image->numCols; j++) {
+	    if (image->data.F32[i][j] >= SATURATION) {
+		mask->data.U8[i][j] |= PSPHOT_MASK_SATURATED;
+	    }
+	}
+    }
+
+    psLogMsg ("psphot", 4, "load data: %f sec\n", psTimerMark ("psphot"));
+
+    // save the image data & return it
+    eamReadout *imdata = eamReadoutAlloc(image, weight, mask, header);
+    return (imdata);
+}
Index: /tags/psphot_dev_02/psphot/src/psphotSortBySN.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotSortBySN.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotSortBySN.c	(revision 5352)
@@ -0,0 +1,19 @@
+# include "psphot.h"
+
+// sort by SN (descending)
+int psphotSortBySN (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->moments == NULL) ? 0 : A->moments->SN;
+    psF32 fB = (B->moments == NULL) ? 0 : B->moments->SN;
+    if (isnan (fA)) fA = 0;
+    if (isnan (fB)) fB = 0;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (-1);
+    if (diff < FLT_EPSILON) return (+1);
+    return (0);
+}
+
Index: /tags/psphot_dev_02/psphot/src/psphotSourceStats.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotSourceStats.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotSourceStats.c	(revision 5352)
@@ -0,0 +1,53 @@
+# include "psphot.h"
+
+psArray *psphotSourceStats (eamReadout *imdata, psMetadata *config, psArray *peaks) 
+{
+    bool     status  = false;
+    psArray *sources = NULL;
+
+    psTimerStart ("psphot");
+
+    // determine properties (sky, moments) of initial sources
+    float INNER    = psMetadataLookupF32 (&status, config, "SKY_INNER_RADIUS");
+    float OUTER    = psMetadataLookupF32 (&status, config, "SKY_OUTER_RADIUS");
+    float RADIUS   = psMetadataLookupF32 (&status, config, "PSF_MOMENTS_RADIUS");
+
+    sources = psArrayAlloc (peaks->n);
+    sources->n = 0;
+
+    for (int i = 0; i < peaks->n; i++) {
+
+	// create a new source, add peak
+	pmSource *source = pmSourceAlloc();
+	source->peak = (pmPeak *)psMemCopy(peaks->data[i]);
+
+	// allocate image, weight, mask arrays for each peak (square of radius OUTER)
+	// XXX EAM : this has 12 leaks (v.5)
+	psphotDefinePixels (source, imdata, source->peak->x, source->peak->y, OUTER);
+
+	// measure a local sky value
+	// XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+	status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER);
+	if (!status) {
+	  psFree (source);
+	  continue;
+	}
+
+	// measure basic source moments
+	// XXX EAM : choose between these two versions
+	status = pmSourceMoments (source, RADIUS);
+	if (!status) {
+	  psFree (source);
+	  continue;
+	}
+	
+	// add to the source array
+	psArrayAdd (sources, 100, source);
+	psFree (source);
+    }
+
+    psLogMsg ("psphot", 3, "%d moments: %f sec\n", sources->n, psTimerMark ("psphot"));
+    return (sources);
+}
+
+// XXX EAM : filter out bad peaks (eg, no valid pixels available for sky)
Index: /tags/psphot_dev_02/psphot/src/psphotSubtractPSF.c
===================================================================
--- /tags/psphot_dev_02/psphot/src/psphotSubtractPSF.c	(revision 5352)
+++ /tags/psphot_dev_02/psphot/src/psphotSubtractPSF.c	(revision 5352)
@@ -0,0 +1,36 @@
+# include "psphot.h"
+
+// subtract model fits from image (only PSFSTAR && BRIGHTSTAR)
+// need to control application of this with some thresholding
+
+bool psphotSubtractPSF (pmSource *source)
+{ 
+  float sky;
+  pmModel *model;
+  psImage *pixels;
+
+  // only subtract successful fits
+  if (source->type == PM_SOURCE_SATSTAR) goto valid;
+  if (source->type == PM_SOURCE_PSFSTAR) goto valid;
+  if (source->type == PM_SOURCE_GOODSTAR) goto valid;
+  return (false);
+
+valid:
+  model = source->modelPSF;
+  if (model == NULL) {
+    psLogMsg ("psphot.subtract_psf_source", 2, "missing model for %f, %f\n", source->moments->x, source->moments->y);
+    return (false); 
+  }	    
+
+  pixels = source->pixels;
+
+  // subtract object, leave local sky
+  sky = model->params->data.F32[0];
+  model->params->data.F32[0] = 0;
+  pmSourceSubModel (pixels, source->mask, model, false);
+  model->params->data.F32[0] = sky;
+
+  // XXX EAM : amplify the weight matrix a la dophot?
+
+  return (true);
+}
