Index: /branches/eam_branches/stackphot.20100406/psphot/Doxyfile.in
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/Doxyfile.in	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/Doxyfile.in	(revision 27622)
@@ -0,0 +1,1310 @@
+# Doxyfile 1.5.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file that 
+# follow. The default is UTF-8 which is also the encoding used for all text before 
+# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into 
+# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of 
+# possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = psphot
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = ipp-2.7.dev
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = ./docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, 
+# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, 
+# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, 
+# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to 
+# include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct (or union) is 
+# documented as struct with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code where the coding convention is that all structs are 
+# typedef'ed and only the typedef is referenced never the struct's name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be extracted 
+# and appear in the documentation as a namespace called 'anonymous_namespace{file}', 
+# where file will be replaced with the base name of the file that contains the anonymous 
+# namespace. By default anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = ./src
+
+# This tag can be used to specify the character encoding of the source files that 
+# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
+# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. 
+# See http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = 
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the output. 
+# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, 
+# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH 
+# then you must also enable this option. If you don't then doxygen will produce 
+# a warning and turn it on anyway
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to 
+# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to 
+# specify the directory where the mscgen tool resides. If left empty the tool is assumed to 
+# be found in the default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a caller dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable caller graphs for selected 
+# functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the number 
+# of direct children of the root node in a graph is already larger than 
+# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /branches/eam_branches/stackphot.20100406/psphot/Makefile.am
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/Makefile.am	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/Makefile.am	(revision 27622)
@@ -0,0 +1,12 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+bin_SCRIPTS = psphot-config 
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psphot.pc
+
+EXTRA_DIST = \
+	psphot-config.in \
+	psphot.pc.in \
+	autogen.sh
Index: /branches/eam_branches/stackphot.20100406/psphot/autogen.sh
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/autogen.sh	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/autogen.sh	(revision 27622)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=psphot
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOLIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOLIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/stackphot.20100406/psphot/configure.ac
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/configure.ac	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/configure.ac	(revision 27622)
@@ -0,0 +1,219 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([psphot], [0.9.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------------ kapa,libkapa options -------------------------
+dnl -- libkapa implies the requirement for libpng, libjpeg as well --
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+dnl test for command-line options: use ohana-config if not supplied
+KAPA_CFLAGS_CONFIG="true"
+KAPA_LIBS_CONFIG="true"
+AC_ARG_WITH(kapa,
+[AS_HELP_STRING(--with-kapa=DIR,Specify location of libkapa)],
+[KAPA_CFLAGS="-I$withval/include" KAPA_LIBS="-L$withval/lib" 
+ KAPA_CFLAGS_CONFIG="false"       KAPA_LIBS_CONFIG="false"])
+AC_ARG_WITH(kapa-include,
+[AS_HELP_STRING(--with-kapa-include=DIR,Specify libkapa include directory.)],
+[KAPA_CFLAGS="-I$withval" KAPA_CFLAGS_CONFIG="false"])
+AC_ARG_WITH(kapa-lib,
+[AS_HELP_STRING(--with-kapa-lib=DIR,Specify libkapa library directory.)],
+[KAPA_LIBS="-L$withval" KAPA_LIBS_CONFIG="false"])
+
+echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+echo "KAPA_LIBS_CONFIG: $KAPA_LIBS_CONFIG"
+echo "KAPA_CFLAGS: $KAPA_CFLAGS"
+echo "KAPA_LIBS: $KAPA_LIBS"
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+HAVE_KAPA="true"
+AC_MSG_NOTICE([checking for libkapa])
+if test "$KAPA_CFLAGS_CONFIG" = "true" -o "$KAPA_LIBS_CONFIG" = "true"; then
+  AC_MSG_NOTICE([kapa info supplied by ohana-config])
+  KAPA_CONFIG=`which ohana-config`
+  AC_CHECK_FILE($KAPA_CONFIG,[],
+    [HAVE_KAPA="false"; AC_MSG_WARN([libkapa is not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location])])
+  
+  echo "HAVE_KAPA: $HAVE_KAPA"
+  echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_CFLAGS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa cflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa cflags])
+   KAPA_CFLAGS="`${KAPA_CONFIG} --cflags`"
+   AC_MSG_RESULT([${KAPA_CFLAGS}])
+  fi
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_LIBS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa ldflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa ldflags])
+   KAPA_LIBS="`${KAPA_CONFIG} --libs` -lX11"
+   AC_MSG_RESULT([${KAPA_LIBS}])
+  fi
+fi
+
+if test "$HAVE_KAPA" = "true" ; then
+ AC_MSG_NOTICE([libkapa supplied])
+ PSPHOT_CFLAGS="${PSPHOT_CFLAGS} ${KAPA_CFLAGS}"
+ PSPHOT_LIBS="${PSPHOT_LIBS} ${KAPA_LIBS}"
+else
+ AC_MSG_NOTICE([libkapa ignored])
+fi
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+dnl HAVE_KAPA=true
+dnl AC_CHECK_HEADERS([kapa.h],
+dnl  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $KAPA_CFLAGS" AC_SUBST(KAPA_CFLAGS)],
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])]
+dnl )
+dnl AC_CHECK_LIB(kapa,KapaInitGraph,
+dnl  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],  
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])],[-lm]
+dnl )
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libjpeg options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(jpeg,
+[AS_HELP_STRING(--with-jpeg=DIR,Specify location of libjpeg.)],
+[JPEG_CFLAGS="-I$withval/include"
+ JPEG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(jpeg-include,
+[AS_HELP_STRING(--with-jpeg-include=DIR,Specify libjpeg include directory.)],
+[JPEG_CFLAGS="-I$withval"])
+AC_ARG_WITH(jpeg-lib,
+[AS_HELP_STRING(--with-jpeg-lib=DIR,Specify libjpeg library directory.)],
+[JPEG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${JPEG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${JPEG_LDFLAGS}"
+
+AC_CHECK_HEADERS([jpeglib.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $JPEG_CFLAGS" AC_SUBST(JPEG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg headers not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg library not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libpng options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(png,
+[AS_HELP_STRING(--with-png=DIR,Specify location of libpng.)],
+[PNG_CFLAGS="-I$withval/include"
+ PNG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(png-include,
+[AS_HELP_STRING(--with-png-include=DIR,Specify libpng include directory.)],
+[PNG_CFLAGS="-I$withval"])
+AC_ARG_WITH(png-lib,
+[AS_HELP_STRING(--with-png-lib=DIR,Specify libpng library directory.)],
+[PNG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${PNG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${PNG_LDFLAGS}"
+
+AC_CHECK_HEADERS([png.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $PNG_CFLAGS" AC_SUBST(PNG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng headers not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+AC_CHECK_LIB(png,png_init_io,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $PNG_LDFLAGS -lpng"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng library not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ use kapa or not? ---------------------
+
+if test "$HAVE_KAPA" == "true" ; then
+  AC_MSG_RESULT([including plotting functions])
+  AC_DEFINE([HAVE_KAPA],[1],[enable use of libkapa])
+else
+  AC_MSG_RESULT([skipping plotting functions])
+  AC_DEFINE([HAVE_KAPA],[0],[disable use of libkapa])
+fi
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+
+CFLAGS="${CFLAGS=} -Wall -Werror"
+echo "PSPHOT_CFLAGS: $PSPHOT_CFLAGS"
+echo "PSPHOT_LIBS: $PSPHOT_LIBS"
+
+IPP_VERSION
+
+AC_SUBST([PSPHOT_CFLAGS])
+AC_SUBST([PSPHOT_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  psphot-config
+  psphot.pc
+  Doxyfile
+])
+
+AC_OUTPUT
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/2Dmodels.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/2Dmodels.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/2Dmodels.txt	(revision 27622)
@@ -0,0 +1,29 @@
+
+I am testing a new concept for the 2D representation of various values
+measured by psphot.  I have been using 2D polynomials to fit the
+variations of, eg, the psf parameters as a function of position in the
+images.  this has been somewhat successful, but it has also been
+fraught with a number of problems.  the worst issue has been the fact
+that the polynomial form is not physically motivated.  in some cases,
+eg the variation of the psf shape parameters, the true variations for
+real distributions are not well represented by a polynomial.  I have
+done some ad hoc tricks to get the functional form to look roughly
+like a polynomial, but this is really quite a hack.  the other problem
+is that it is difficult to decide if the polynomial does an acceptable
+job representing the variations.
+
+My new concept for modeling 2D variations in some parameter is to
+define a low-resolution 2D image to represent the variation.  The
+advantage is that the image may be define with whatever resolution may
+be sampled by the input data, and the functional form need not be
+constrained by the polynomial basis function.  
+
+** is this a poor substitute for using a chebychev polynomial as a
+   better basis function?
+
+psphot parameters which vary by position:
+
+pmPSF.params 
+pmPSF.ApTrend
+pmPSF.FluxScale (flux given normalization of 1.0)
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/config.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/config.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/config.txt	(revision 27622)
@@ -0,0 +1,56 @@
+
+# input data options
+# IMAGE             STR    file.fits
+# MASK_IMAGE	    STR    mask.fits
+# WEIGHT_IMAGE	    STR	   weight.fits
+
+# output data options
+OUTPUT_FILE	    STR	   output.smp    # output object file
+OUTPUT_MODE	    STR	   TEXT          # output mode: TEXT, OBJ, SX, CMP, CMF 
+# RESID_IMAGE	    STR	   resid.fits    # output residual image
+
+# image noise parameters
+RDNOISE             STR  HD:RDNOISE      # read-noise in electrons
+GAIN                STR  HD:GAIN         # electrons / DN
+
+# masking parameters
+XMIN                F32  32        	 # minimum valid x-coord
+XMAX                F32   0        	 # maximum valid x-coord
+YMIN                F32   1        	 # minimum valid y-coord
+YMAX                F32   0        	 # maximum valid y-coord
+SATURATION          F32  50000     	 # saturation level on this chip
+
+# image statistics parameters
+IMSTATS_NPIX        S32  100000    	 # number of pixels to use for sky estimate:
+
+# peak finding 
+PEAKS_SMOOTH_SIGMA  F32  1.0       	 # smoothing kernel sigma in pixels
+PEAKS_SMOOTH_NSIGMA F32  3.0   	   	 # smoothing kernel width in sigmas
+PEAKS_NSIGMA_LIMIT  F32  10.0  	   	 # peak significance threshold
+
+# basic object statistics
+SKY_INNER_RADIUS    F32  15		 # square annulus for local sky measurement
+SKY_OUTER_RADIUS    F32  25		 # square annulus for local sky measurement
+PSF_MOMENTS_RADIUS  F32  5
+PSF_SN_LIM          F32  100
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+#PSF_MODEL           STR  PS_MODEL_PGAUSS
+#PSF_MODEL           STR  PS_MODEL_GAUSS
+PSF_FIT_RADIUS      F32  25		 # fitting radius for test PSF model
+
+# PSF model parameters : apply the PSF model
+PSF_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+PSF_FIT_PADDING      F32  5              # extra annulus to use for fit 
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MIN_SN           F32  2.0		 # reject objects below this significance
+PSF_MAX_CHI          F32  10.0		 # reject objects worse that this
+
+# Galaxy model parameters
+GAL_MODEL            STR  PS_MODEL_SGAUSS
+GAL_MIN_SN           F32  3
+GAL_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+GAL_FIT_PADDING      F32  5              # extra annulus to use for fit 
+GAL_MOMENTS_RADIUS   F32  9
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/efficiency.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/efficiency.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/efficiency.txt	(revision 27622)
@@ -0,0 +1,51 @@
+Strategy for efficiency analysis (a.k.a. "fake" stage)
+
+Want:
+* Detection efficiency as a function of instrumental magnitude
+* Masked fraction
+
+Given:
+* Image
+* Mask
+* Variance
+* PSF
+* Sources on image
+* Recipe:
+  + Magnitude bins (relative to guessed detection limit) for fake sources
+  + Number of fake sources for each bin
+
+
+
+Algorithm:
+* Determine mean instrumental magnitude detection limit
+  + Have:
+    - Mean variance
+    - Smoothing size
+    - Covariance
+    - Threshold
+  + Calculate mean peak flux of source at threshold
+  + Integrate PSF to determine magnitude
+* Remove all real sources from image
+* Add fake sources into image
+* Smooth image, variance (maybe only at positions of fake sources?)
+* Count number of sources masked
+* Count number of fake sources above significance level as a function of fake source magnitude
+
+
+If the required density of fake sources is so high that they begin to
+overlap, we will need to do add sources, smooth, and count
+independently for each magnitude bin.  This could be optimised
+using the distributive property of convolutions (f*[g+h]=f*g+f*h)
+if we smooth the image first, and then add in smoothed PSFs.
+
+
+For a 5% statistical measurement of the detection efficiency per bin, we want
+dN/N = 0.05 ==> N ~ 400 for Poisson statistics.
+Assume a 80% masked fraction, so want 500 sources per bin.
+For 5 magnitude bins, want 2500 sources
+Assume worst case PSF is 15 pix FWHM --> ~ 51 pixels for 4-sigma either side
+Total area is 6502500 pixels.
+Chip images are 4846x4868 = 23590328 pixels = 3.6 times the worst total area.
+Not too bad (especially since this is pretty much worst case, and we only
+care about the central pixel, which is much smaller)
+==> Let's just throw all sources on the same image.
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/footprints.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/footprints.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/footprints.txt	(revision 27622)
@@ -0,0 +1,61 @@
+
+2010.01.18
+
+  * we are having some problems with footprints being freed to early in the second pass
+
+  * footprint memory history:
+
+    * entering psphotFindFootprints, on the first pass, we have an
+      array of detected peak in detections->peaks
+
+    * in pmFootprintsFind, footprints array owns allocated footprints
+      footprints each own their spans
+
+    * from pmFootprintAssignPeaks:
+      * the footprints have an array of peaks with valid memory references
+      * the peaks have just the pointer to the corresponding footprint (not owned)
+      * all peaks get assigned to a footprint
+      * at end of pmFootprintAssignPeaks, only a single copy of each peak is/should be available from the collection of footprints
+
+pmFootprintCullPeaks is very expensive (15 - 30 msec per object) and
+must be performed on every peak.  We need to improve the speed (even
+at the cost of memory) and we need to thread it.
+
+*** pmFootprints makes use of the mask image, but it does not use the
+    master mask values.  this must be fixed!
+
+-- here is the algorithm
+
+** note: this is run once for each footprint...
+
+* the incoming footprint peak list is already sorted by brightness
+
+* loop over all peaks
+
+  * choose a threshold which is the peak flux - nsigma_delta*stdev
+    ? actually, the threshold is using the edge of the containing
+    subimage?  I'm a little confused by this.
+
+  * if the threshold is below a minimum threshold, we drop it
+    - the code is calling psArrayRemoveIndex for each of these, then decrementing i.
+      this is inefficient since psArray is not a psList.  two possible options here:
+      
+      * do not remove the dropped peaks from the array until the full
+        processing is done, then re-build the array in a single pass.
+        flag peaks to be dropped (either in the structure or with a
+        parallel vector)
+
+      * have the code build a separate 'bright peaks' array on the fly
+        then swap the new peaks out under the pmFootprint.
+
+  * psArrayRemoveIndex is used 3x in pmFootprints.c : review each case
+
+  * the subImage is divided into footprints above threshold about the
+    current peak.
+
+    - EAM : Need to disect pmFindFootprintsAtPoint.  Is it expensive
+        in this case?  Should we be calling the normal pmFootprints
+        function?
+
+
+  
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20090523.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20090523.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20090523.txt	(revision 27622)
@@ -0,0 +1,123 @@
+
+20090809 : more on extended sources:
+
+ my algorithm for getting the petrosian radii and fluxes is:
+
+  * measure radial profile by interpolation to specific locations along the radial line
+    (at low radii, this gives much more accurate results; at high radii, we may need to 
+    average a group of pixels -- say, for 15 deg separations, choose a box that is 
+    < 5 degree in width -- this is > 1 pixel at a distance of 12 pixels.
+
+    psphotRadialProfilesByAngle
+
+  * find intersection of profile with isophot
+
+    psphotRadiusFromProfile -> r50(theta)
+
+  * generate r50x, r50y and fit to ellipse
+
+    psphotEllipticalContour
+
+  * generate elliptical profile
+
+    psphotEllipticalProfile
+
+  * convert to \alpha r_i < r < \beta r_i bins
+
+    psphotPetrosianRadialBins
+
+20090725 : extended source radial profiles
+
+ I need to fix the Petrosian and similar code.  There are several issues:
+
+ 1) correct implementation of the actual Petrosian radius / Petrosian flux measurement:
+
+    R_p : Given F(R) : F(0) / F(R_p) = 0.9 ?? (something like this...)
+
+    Actually implemented by calculating the average surface brightness in annullii:
+
+    R_p : Given F(R) -> define f(R) = \sum_{R-dR}^{R+dR}F(R) / 4\pi R dR
+    R_p : f(dR) / F(R_p) = P_ratio
+
+ 2) need to construct a clean, normalized radial profile to make this measurement
+
+    * generate radial profiles for each of N annular wedges
+    * find R_50 : f(0) / f(R_50) = 0.5
+    $ fit the ellipse of R_50 to Rmaj sin(T) + Rmin cos(T) 
+
+
+    cos(a+b) = cos(a) cos(b) - sin(a) sin(b)
+    sin(a+b) = cos(a) sin(b) + cos(b) sin(a)
+
+    cos(theta+To) = cos(theta) cos(To) - sin(theta) sin(To)
+    sin(theta+To) = cos(theta) sin(To) + sin(theta) cos(To)
+
+    R_x = R_maj cos(theta + theta_o) = R_maj cos(theta) cos(theta_o) - Rmaj sin(theta) sin(theta_o)
+    R_y = R_min sin(theta + theta_o) = R_min cos(theta) sin(theta_o) + Rmin sin(theta) cos(theta_o)
+
+    R_X = R_x cos(phi) - R_y sin(phi)
+    R_Y = R_x sin(phi) + R_y cos(phi)
+
+    R_X = R_maj cos(phi) cos(theta + To) - R_min sin(phi) sin(theta + To)
+    R_Y = R_maj sin(phi) cos(theta + To) + R_min cos(phi) sin(theta + To)
+
+    R_X = (R_maj cos(phi) cos (To) - R_min sin(phi) sin(To)) cos(theta) - (Rmaj cos(phi) sin(To) + Rmin sin(phi) cos(To)) sin(theta)
+    R_Y = (R_maj sin(phi) cos (To) + R_min cos(phi) sin(To)) cos(theta) - (Rmaj cos(phi) sin(To) + Rmin sin(phi) cos(To)) sin(theta)
+    
+
+    R_X = Ax cos(theta) + Bx sin(theta)
+    R_Y = Ay cos(theta) + By sin(theta)
+    
+    Ax = \sum (R_X * cos(theta))
+    Bx = \sum (R_X * sin(theta))
+    Ay = \sum (R_Y * cos(theta))
+    By = \sum (R_Y * sin(theta))
+
+    R_maj = sqrt(Ax^2 + Ay^2)
+    R_min = sqrt(Bx^2 + By^2)
+
+    phi = atan2(Bx + By, Ax + Ay)
+
+    *** get rest of math from my notebook...
+
+20090525 : some clarity of issues:
+
+ I've made some progress understanding why the moments analysis was
+ giving poor results.  I've added Gaussian (pseudo-gaussian)
+ windowing and the ability to set the per-pixel S/N limit.  The first
+ important conclusion was:
+
+ * the per-pixel S/N limit of 1.0 was driving the Mxx,Myy values low
+   for faint sources.  This should be completely disabled.
+
+ The next point is about the Gaussian vs tophat windowing.  One
+ interesting realization was that the Gaussian windowing (or tophat
+ for that matter) have a stronger impact on the measured moments for
+ sources with profiles that are non-Gaussian.  
+
+ * Gauss with seeing (FWHM) = 1.0 arcsec = 4pix -> sigma = 1.702 
+
+   Gauss window | measured Mxx
+   2.0	 	  1.75
+   4.0		  2.58
+   6.0		  2.85
+   
+ * QGAUSS with see 
+
+20090523 : extended source analysis -- further development
+
+ issues to address:
+
+ * quality of moments used for initial psf selection
+   - what is the behavior of moments with Gaussian windowing as function of S/N & size?
+   - can we choose a sigma value that will give reliable moments for an expected range of seeing?
+
+ * moments for galaxy model guesses
+   - trade-off between Gaussian windowing, tophat windowing, and S/N windowing in footprint area
+
+ initial dev work:
+
+ * add Gaussian windowing, S/N windowing, (tophat windowing) and options to select
+
+ * check visualization of moments
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20091026.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20091026.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20091026.txt	(revision 27622)
@@ -0,0 +1,44 @@
+
+2009.10.26
+
+  I'm looking into psphot as applied to diff images.  For now, I am
+  running a comparison of a simtest output warp with the convolved
+  version of the same image as generated by ppSub.  
+
+  * we are losing the cores of bright stars, probably to the choices
+    in the FITS output 16-bit conversion (pixel-value -> NAN)
+
+  * errors for the convolved image seem to be smaller for the same
+    instrumental mags than for the warp (check chip).
+
+  * the psf model seems to be quite poor -- a lot of structure in the
+    psf core.
+
+  * (note: not a very good match with the warp image, though better)
+
+  * psfsub images seem to be slightly displaced in X-dir.  perhaps
+    because the convolution kernel has a bit of X-dir tilt in the
+    peak.  Try with PPSUB ISIS orders set to 0.0 
+
+2009.10.27
+
+  I need some psphot options / variants:
+
+  * forced photometry:
+
+    I should be able to do:
+    
+    psphotForced -file input.fits -psf psf.fits -src src.list output 
+    
+    and have it only perform the forced photometry on the sources listed
+    in src.list.
+
+  * psf model creation
+
+    I should be able to do:
+
+    psphotMakePSF -file input.fits -src src.list output
+
+    and have it generate a PSF model using the sources listed in
+    src.list
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20100131.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20100131.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/notes.20100131.txt	(revision 27622)
@@ -0,0 +1,66 @@
+
+2010.03.24
+
+ I have finished another iteration on source size analysis.  I feel
+ fairly confident of the extended source and CR identifications.  They
+ are not perfect, but they are in pretty good shape.  The CR IDs use
+ the moments to find things smaller in one dimension than a PSF, while
+ the EXT is set for things larger than a PSF in one dimention. 
+
+ There still seems to be some trouble with SATSTARS : these are being
+ set for sources which are fainter than the SAT limit -- I'm not sure
+ if this is due to the quality of the PSF fit or if they really are
+ SAT, but have integrated fluxes lower than expected (underfitted in
+ PSF).
+
+ Additional Parameters needed from psphot for distinguishing dipoles:
+
+ -- note regarding CfA parameters -- 
+
+  Here are our settings.
+  
+  Note that Fpos is the flux which is 3-sigma above background, and
+  Fneg is the flux which is 3-sigma below background.
+  
+  Npos is the # of pixels with flux which is 3-sigma above background.
+  
+  etc.
+  
+  # for positive objects:
+  # Ngood = Npos, Nbad = Nneg, Fgood = Fpos, Fbad = Fneg
+
+  # for negative objects:
+  # Ngood = Nneg, Nbad = Npos, Fgood = Fneg, Fbad = Fpos
+  # FRATIO=Fgood/(Fgood+Fbad)
+  # NRATIO_BAD=Ngood/(Ngood+Nbad)
+  # NRATIO_MASK=Ngood/(Ngood+Nmask)
+  # NRATIO_ALL=Ngood/(Ngood+Nbad+Nmask)
+  DC_FRATIO_MIN         0.75   
+  DC_NRATIO_BAD_MIN     0.70   
+  DC_NRATIO_MASK_MIN    0.60
+  DC_NRATIO_ALL_MIN     0.50
+  DC_NGOOD_MIN          3
+
+  *** double-check on the use of fluxScale in pmSourcePhotometry.c:106
+
+20100131
+
+  We have several stand-alone psphot versions, and a few different
+  psphotReadout versions called by other programs.  Here is a complete
+  list:
+
+  Stand-alone psphot versions | associated psphotReadout version
+
+  o psphot | psphotReadout
+  o psphotForced | psphotForcedReadout
+  o psphotMakePSF | psphotMakePSFReadout 
+  x psphotDetect | psphotDetectReadout
+  x psphotStack | psphotStackReadout
+
+  External programs | associated psphotReadout version
+
+  o ppImage | psphotReadout
+  o pswarp | psphotReadoutFindPSF
+  o ppSub  | psphotReadoutFindPSF, psphotReadoutMinimal
+  o ppStack | psphotReadoutKnownSources
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/notes.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/notes.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/notes.txt	(revision 27622)
@@ -0,0 +1,971 @@
+
+2009.09.26
+
+  Remaining PSPHOT Issues:
+
+  o soften errors a bit for bright stars?  (essentially allow a
+    systematic error on the flat of some percentage).  this may help
+    the very saturated stars (peaks > 10k) which seem to be
+    excessively driven by the cores.
+    ** did not seem to be a big success --> makes things worse for f =
+    0.01, about the same for f = 0.005
+
+  o clean up and remove test bits of code.
+
+  o clean up psphot.config files : drop unused, fix some conflicting
+    names, reduce camera-specific options, make S/N limits
+    consistent...
+
+  * PSF Eval & ChiSq test??
+
+  * EXT fail fit?  lack of iterations?
+
+  * extended source output
+
+  * still some concerns with the source size analysis: the parameters
+    space seems fine (Mxx, Myy, nSigma), but we need to define more
+    sophisticated boundaries (perhaps include dMag or Mag).  
+
+  * some optimization issues:	      
+    * too many re-calculations of all magnitude / aperture data
+    * is FluxScale really faster?  do we need a finer mapping? (5x5
+      clearly yields too much scatter)
+    * is psphotFitSourcesLinear.c (build matrix) super-slow when optimized?
+
+  * very saturated stars are somewhat segmented.
+
+  * footprint coverage in dense simtest image 005.001?
+
+  * footprint S/N limit vs peaks S/N limit?
+
+  * warning about burntool table?
+
+  * is the residual map failing to be masked at the radius?
+
+  * PSF 2D convergence : how to choose between marginally different
+    options?  (note that the GPC1 images may go to 2x2 instead of 3x3)
+
+  * -visual needs more fine-grained control
+
+  * visualization graphs should reset the sections in general.
+
+  * some excess verbosity (my log level is 9, lower?)
+
+  ----- 
+
+  Apparently I'm getting bad psf vs ap mags because of the difference
+  between linear and non-linear fits.  I think this is due to the
+  weighting schemes:
+
+  psphotChoosePSF & psphotBlendFit depend on POISSON.ERRORS.PHOT.LMM,
+  which is set to TRUE => use Poisson errors
+
+  psphotFitSourcesLinear depends on CONSTANT_PHOTOMETRIC_WEIGHTS,
+  which is set to TRUE => do not use Poisson errors.
+
+  nothing seems to use the value POISSON.ERRORS.PHOT.LIN
+
+  ** Poisson errors seeem to be working much better than Constant
+     errors (as judged by consistency between PSF and APERTURE
+     magnitdues).  Not sure I understand this.  It could be that the
+     implementation of Constant errors in the linear fitting analysis
+     is busted.  
+
+  ** Also, I was getting some additional scatter from the FluxScale
+     (lookup-table for Io -> mags).  
+
+     * surprisingly, it is not clear that the FluxScale method is
+       faster than the more accurate integration technique...
+
+2009.09.25
+
+  Notes on pmSourceMagnitudes:
+
+  * in psphotChoosePSF, in pmPSFtry: raw PSF & raw AP
+  * in psphotChoosePSF, psphotMakeGrowthCurve: uses direct all to pmSourcePhotometryAper
+  * in psphotSourceSize, in psphotSourceSizePSF, raw PSF & raw AP (but uses moments->Sum) for PSF stars
+    NOTE: on the initial call, the PSF stars may have had multiple models tried and the psfMag
+    may not be the value for the chosen model.  
+  * in psphotSourceSize, in psphotSourceClassRegion, raw PSF & raw AP for all sources for which size is not yet measured.
+  * in psphotApResid: raw PSF & raw AP for STARS only
+  * in psphotMagnitudes; corrected PSF & AP mags
+
+2009.01.24
+
+  Some results from the threading work below.  these are all tests on
+  pikake using a sample GPC1 image.  This set uses an optimized
+  build of psLib, psModules, and psphot.
+
+pikake: grep --color "built models" o4771g0244o.t?.opt.log
+o4771g0244o.t0.opt.log:    built models for 3156 objects: 0.696490 sec
+o4771g0244o.t0.opt.log:    built models for 1738 objects: 0.367092 sec
+o4771g0244o.t1.opt.log:    built models for 3150 objects: 0.746428 sec
+o4771g0244o.t1.opt.log:    built models for 1745 objects: 0.411406 sec
+o4771g0244o.t2.opt.log:    built models for 3152 objects: 0.412824 sec
+o4771g0244o.t2.opt.log:    built models for 1756 objects: 0.241859 sec
+o4771g0244o.t3.opt.log:    built models for 3151 objects: 0.341083 sec
+o4771g0244o.t3.opt.log:    built models for 1746 objects: 0.176361 sec
+o4771g0244o.t4.opt.log:    built models for 3159 objects: 0.235669 sec
+o4771g0244o.t4.opt.log:    built models for 1741 objects: 0.164474 sec
+o4771g0244o.t5.opt.log:    built models for 3155 objects: 0.286719 sec
+o4771g0244o.t5.opt.log:    built models for 1747 objects: 0.165341 sec
+
+pikake: grep --color "measure magnitudes" o4771g0244o.t?.opt.log
+o4771g0244o.t0.opt.log:    measure magnitudes : 4.080628 sec for 4895 objects (2191 with apertures)
+o4771g0244o.t1.opt.log:    measure magnitudes : 4.155192 sec for 4896 objects (2185 with apertures)
+o4771g0244o.t2.opt.log:    measure magnitudes : 2.174322 sec for 4909 objects (2190 with apertures)
+o4771g0244o.t3.opt.log:    measure magnitudes : 1.507431 sec for 4898 objects (2189 with apertures)
+o4771g0244o.t4.opt.log:    measure magnitudes : 1.174447 sec for 4901 objects (2191 with apertures)
+o4771g0244o.t5.opt.log:    measure magnitudes : 1.149875 sec for 4903 objects (2194 with apertures)
+
+pikake: grep --color "full-frame aperture" o4771g0244o.t?.opt.log
+o4771g0244o.t0.opt.log:    measure full-frame aperture residuals for 1977 sources: 1.681260 sec
+o4771g0244o.t1.opt.log:    measure full-frame aperture residuals for 1972 sources: 1.728578 sec
+o4771g0244o.t2.opt.log:    measure full-frame aperture residuals for 1980 sources: 0.929584 sec
+o4771g0244o.t3.opt.log:    measure full-frame aperture residuals for 1978 sources: 0.656556 sec
+o4771g0244o.t4.opt.log:    measure full-frame aperture residuals for 1977 sources: 0.507413 sec
+o4771g0244o.t5.opt.log:    measure full-frame aperture residuals for 1981 sources: 0.512259 sec
+
+pikake: grep --color "fit models" o4771g0244o.t?.opt.log
+o4771g0244o.t0.opt.log:    fit models: 4.623715 sec for 2011 objects (1904 psf, 45 ext, 62 failed, 1146 skipped)
+o4771g0244o.t1.opt.log:    fit models: 5.036680 sec for 2006 objects (1901 psf, 45 ext, 60 failed, 1145 skipped)
+o4771g0244o.t2.opt.log:    fit models: 3.507495 sec for 2014 objects (1912 psf, 44 ext, 58 failed, 1139 skipped)
+o4771g0244o.t3.opt.log:    fit models: 3.236495 sec for 2012 objects (1906 psf, 44 ext, 62 failed, 1140 skipped)
+o4771g0244o.t4.opt.log:    fit models: 3.929518 sec for 2013 objects (1906 psf, 44 ext, 63 failed, 1147 skipped)
+o4771g0244o.t5.opt.log:    fit models: 4.152286 sec for 2012 objects (1903 psf, 45 ext, 64 failed, 1144 skipped)
+
+pikake: grep failed o4771g0244o.t?.opt.log | grep --color "moments"
+o4771g0244o.t0.opt.log:    3161 sources, 3147 moments, 14 failed: 1.288588 sec
+o4771g0244o.t0.opt.log:    1738 sources, 1698 moments, 40 failed: 0.700303 sec
+o4771g0244o.t1.opt.log:    3155 sources, 3142 moments, 13 failed: 1.329107 sec
+o4771g0244o.t1.opt.log:    1745 sources, 1702 moments, 43 failed: 0.739017 sec
+o4771g0244o.t2.opt.log:    3157 sources, 3144 moments, 13 failed: 0.717423 sec
+o4771g0244o.t2.opt.log:    1756 sources, 1712 moments, 44 failed: 0.409145 sec
+o4771g0244o.t3.opt.log:    3156 sources, 3142 moments, 14 failed: 0.510468 sec
+o4771g0244o.t3.opt.log:    1746 sources, 1702 moments, 44 failed: 0.311198 sec
+o4771g0244o.t4.opt.log:    3164 sources, 3150 moments, 14 failed: 0.412192 sec
+o4771g0244o.t4.opt.log:    1741 sources, 1698 moments, 43 failed: 0.240172 sec
+o4771g0244o.t5.opt.log:    3160 sources, 3144 moments, 16 failed: 0.418781 sec
+o4771g0244o.t5.opt.log:    1747 sources, 1701 moments, 46 failed: 0.244808 sec
+
+pikake: grep --color "complete psphot" o4771g0244o.t?.opt.log
+o4771g0244o.t0.opt.log:    complete psphot run: 38.044034 sec
+o4771g0244o.t1.opt.log:    complete psphot run: 39.039324 sec
+o4771g0244o.t2.opt.log:    complete psphot run: 28.565228 sec
+o4771g0244o.t3.opt.log:    complete psphot run: 25.380312 sec
+o4771g0244o.t4.opt.log:    complete psphot run: 24.606107 sec
+o4771g0244o.t5.opt.log:    complete psphot run: 24.796387 sec
+
+  Some results from the threading work below.  these are all tests on
+  pikake using a sample GPC1 image.  This set uses an unoptimized
+  build of psphot (though psLib may be optmized).
+
+pikake: grep --color "built models" o4771g0244o.t?.no-opt.log
+o4771g0244o.t0.no-opt.log:    built models for 3160 objects: 0.997456 sec
+o4771g0244o.t0.no-opt.log:    built models for 1725 objects: 0.523033 sec
+o4771g0244o.t1.no-opt.log:    built models for 3154 objects: 1.031943 sec
+o4771g0244o.t1.no-opt.log:    built models for 1725 objects: 0.562166 sec
+o4771g0244o.t2.no-opt.log:    built models for 3156 objects: 0.563504 sec
+o4771g0244o.t2.no-opt.log:    built models for 1729 objects: 0.311835 sec
+o4771g0244o.t3.no-opt.log:    built models for 3155 objects: 0.399958 sec
+o4771g0244o.t3.no-opt.log:    built models for 1728 objects: 0.235203 sec
+o4771g0244o.t4.no-opt.log:    built models for 3153 objects: 0.329366 sec
+o4771g0244o.t4.no-opt.log:    built models for 1726 objects: 0.192076 sec
+o4771g0244o.t5.no-opt.log:    built models for 3155 objects: 0.408434 sec
+o4771g0244o.t5.no-opt.log:    built models for 1734 objects: 0.198095 sec
+
+pikake: grep --color "measure magnitudes" o4771g0244o.t?.no-opt.log
+o4771g0244o.t0.no-opt.log:    measure magnitudes : 5.939663 sec for 4886 objects (2187 with apertures)
+o4771g0244o.t1.no-opt.log:    measure magnitudes : 6.041825 sec for 4880 objects (2189 with apertures)
+o4771g0244o.t2.no-opt.log:    measure magnitudes : 3.126765 sec for 4886 objects (2192 with apertures)
+o4771g0244o.t3.no-opt.log:    measure magnitudes : 2.158470 sec for 4884 objects (2188 with apertures)
+o4771g0244o.t4.no-opt.log:    measure magnitudes : 1.756856 sec for 4880 objects (2192 with apertures)
+o4771g0244o.t5.no-opt.log:    measure magnitudes : 1.697331 sec for 4890 objects (2188 with apertures)
+
+pikake: grep --color "full-frame aperture" o4771g0244o.t?.no-opt.log
+o4771g0244o.t0.no-opt.log:    measure full-frame aperture residuals for 1976 sources: 2.869795 sec
+o4771g0244o.t1.no-opt.log:    measure full-frame aperture residuals for 1973 sources: 2.908194 sec
+o4771g0244o.t2.no-opt.log:    measure full-frame aperture residuals for 1977 sources: 1.545140 sec
+o4771g0244o.t3.no-opt.log:    measure full-frame aperture residuals for 1977 sources: 1.091451 sec
+o4771g0244o.t4.no-opt.log:    measure full-frame aperture residuals for 1976 sources: 0.857477 sec
+o4771g0244o.t5.no-opt.log:    measure full-frame aperture residuals for 1979 sources: 0.860652 sec
+
+pikake: grep --color "fit models" o4771g0244o.t?.no-opt.log
+o4771g0244o.t0.no-opt.log:    fit models: 7.771925 sec for 2011 objects (1902 psf, 45 ext, 64 failed, 1150 skipped)
+o4771g0244o.t1.no-opt.log:    fit models: 8.053570 sec for 2010 objects (1904 psf, 45 ext, 61 failed, 1145 skipped)
+o4771g0244o.t2.no-opt.log:    fit models: 5.063026 sec for 2009 objects (1904 psf, 44 ext, 61 failed, 1148 skipped)
+o4771g0244o.t3.no-opt.log:    fit models: 4.314687 sec for 2012 objects (1905 psf, 44 ext, 63 failed, 1144 skipped)
+o4771g0244o.t4.no-opt.log:    fit models: 4.147600 sec for 2009 objects (1904 psf, 44 ext, 61 failed, 1145 skipped)
+o4771g0244o.t5.no-opt.log:    fit models: 4.207860 sec for 2012 objects (1906 psf, 44 ext, 62 failed, 1144 skipped)
+
+pikake: grep failed o4771g0244o.t?.no-opt.log | grep --color "moments" 
+o4771g0244o.t0.no-opt.log:    3165 sources, 3151 moments, 14 failed: 2.867193 sec
+o4771g0244o.t0.no-opt.log:    1725 sources, 1681 moments, 44 failed: 1.513526 sec
+o4771g0244o.t1.no-opt.log:    3159 sources, 3146 moments, 13 failed: 2.966456 sec
+o4771g0244o.t1.no-opt.log:    1725 sources, 1685 moments, 40 failed: 1.568208 sec
+o4771g0244o.t2.no-opt.log:    3161 sources, 3148 moments, 13 failed: 1.590746 sec
+o4771g0244o.t2.no-opt.log:    1729 sources, 1687 moments, 42 failed: 0.823426 sec
+o4771g0244o.t3.no-opt.log:    3160 sources, 3146 moments, 14 failed: 1.068936 sec
+o4771g0244o.t3.no-opt.log:    1728 sources, 1684 moments, 44 failed: 0.603041 sec
+o4771g0244o.t4.no-opt.log:    3158 sources, 3144 moments, 14 failed: 0.869741 sec
+o4771g0244o.t4.no-opt.log:    1726 sources, 1685 moments, 41 failed: 0.505545 sec
+o4771g0244o.t5.no-opt.log:    3160 sources, 3146 moments, 14 failed: 0.830094 sec
+o4771g0244o.t5.no-opt.log:    1734 sources, 1695 moments, 39 failed: 0.469828 sec
+
+pikake: grep --color "complete psphot" o4771g0244o.t?.no-opt.log
+o4771g0244o.t0.no-opt.log:    complete psphot run: 70.375518 sec
+o4771g0244o.t1.no-opt.log:    complete psphot run: 71.167205 sec
+o4771g0244o.t2.no-opt.log:    complete psphot run: 51.237604 sec
+o4771g0244o.t3.no-opt.log:    complete psphot run: 44.195015 sec
+o4771g0244o.t4.no-opt.log:    complete psphot run: 41.782608 sec
+o4771g0244o.t5.no-opt.log:    complete psphot run: 42.483942 sec
+
+Not bad: almost a factor of 2 overall.  psphotMagnitudes,
+psphotApResid, psphotSourceStats all get nearly the max improvement.
+psphotBlendFit seems to be saturating.  There are a lot of allocs in
+the main functions: pmSourceFitModel and pmSourceFitSet, which are
+probably blocking each other.
+
+2009.01.22
+
+  working on more psphot threading.  So far, I have threaded code for:
+
+  * psphotGuessModel (DONE) : not working so well just yet (see
+    below).  Late Update: in recent tests, the result below may have
+    been caused by the unoptimized psLib.
+
+  here are functions for which a modest gain can be realized and which
+  should be easy to thread:
+
+  * psphotMagnitudes (DONE)
+
+    ** this function uses psImageShiftMask to interpolate the source
+       flux to compensate for fractional pixel offsets.  This function
+       requires a call to psImageAlloc and a subsequent call to
+       psFree.  This probably will make the threading
+       inefficient.  However, it is not clear we need to perform this
+       interpolation for all sources: the correction is probably only
+       signficant compared to the errors for the brighter sources.
+
+    ** psImageShiftMask gratuitously calls psImageInterpolationAlloc;
+       perhaps it should accept this as an argument, or perhaps this
+       function should be re-examined.
+
+  * psphotApResid (DONE)
+
+    * this function is basically calling pmSourceMagnitudes, so
+      suffers the same issues as psphotMagnitudes
+
+  * psphotFindDetections (footprints) (fairly tricky, actually)
+
+    ** need to examine the steps in the function. it is not obvious
+       yet what needs to be improved.
+
+  * psphotBlendFit (DONE)
+
+    ** this one will be a little tricky: this function currently
+       extends the source array to add new sources detected as part of
+       psphotFitBlob.  I would need to have a way of saving these from
+       each thread, then joining them back together at the end.
+
+  * psphotSourceStats (DONE)
+
+    ** this one includes the allocs to generate the source pixels, but
+       could be split into two steps: 1) generate the complete array
+       of sources; 2) measure the sky, sky variance, and moments.
+
+       Otherwise, this function should be easy: no other allocs or
+       frees, i think.
+
+  * psphotChoosePSF : 
+
+    ** need to use the grid technique for the pmPSFtryModel which does
+       the source fitting
+
+2008.10.26
+
+  (note: this is unoptimized)
+  0 threads: built models for 3921 objects: 15.223455 sec
+             built models for 2868 objects: 10.698732 sec
+
+  2 threads: built models for 3921 objects: 37.529655 sec
+             built models for 2862 objects: 28.142142 sec
+
+  (clearly, the threading is doing bad things, at least without optimization..)
+
+  old code (no threading):  
+             built models for 3923 objects: 15.265046 sec
+             built models for 2842 objects: 10.840331 sec
+
+  (so, the threaded version without threading is OK)
+
+2008.06.25
+
+ timing : footprints
+
+ on GPC1, with FFT: 
+    pass 1 :     convolved with grow disc: 17.558344 sec
+    pass 2 :     convolved with grow disc: 8.915036 sec
+
+2008.01.31
+
+ Most of the fixes listed below have been finished except for:
+
+ * careful handling of the r ~ 0 fluxes / effective positions (for eg sersic)
+ * the EXT output parameters have not been tested
+
+ In addition, some further work to be done:
+
+ * multiple image detection
+ * multiple image fitting (including projection)
+ * optimization
+ * multi-threading
+
+ * possibly try the 'active deblending' concept that Ken from WISE is using.
+   * instead of fitting a double star with the positions floating at a
+     slightly random location, search for significant additions within
+     a grid about the central peak (say ~ 1sigma / 0.2 pix?).  
+     (note that this is a linear fit)
+
+ * for the blend groups, freeze the positions for the fainter sources
+   (consistent with the non-linear S/N threshold)
+
+ 
+
+2007.08.17
+
+ I am working on a number of cleanup / fixes.  I have made an overhaul
+ of the pmModel APIs, adding function pointers in the pmModel
+ structure to the class-specific utility functions (eg,
+ pmModel->modelFunc is the actual function which is evaluated). 
+
+ TO DO:
+
+ * update pmSourceFitSet to be able to include more than one model
+   type (currently it assumes the sources are all, eg, PSFs).  This is
+   now needed because the old implementation used the function lookups
+   which have been dropped.
+
+ * define a generic API set to handle 2D modelling of a scalar using
+   either polynomials (as the pmPSF code currently does) or an
+   image-based representation (as the psphot sky model currently
+   does).  
+
+   The image-based representation can automatically step down from NxM
+   super pixels to a smaller number based on the density of
+   measurements.  
+
+   Include ways to smooth and regularize the output result.
+
+   This mechanism can be applied to: psf parameters, aperture
+   residual, psf peak-to-flux variations, the psphot background
+   representation.
+
+ * generate and store the output radial profile for objects
+
+ * finish testing and incorportate the CR / EXT measurements
+
+ * adjustments to pixel center based on second derivatives : needed
+   for the sersic models.
+
+ * adjustments to pixel flux for extreme values (r ~ 0) : needed for
+   the sersic models.
+
+ * on psf stars : fall back on a Gaussian.
+
+ * OPTIMIZATIONS !!
+
+ * drop the model sky element : should only be in source->sky,dsky
+
+2006.11.16
+
+ensemble:
+
+  * first block: 
+    * select sources of interest / skip
+    * generate a guess model
+    * re-measure moments for saturated stars (MOVE)
+    * update peak position (MOVE to peaks)
+    * set the object radius 
+    * build temporary source copies (including pixels, mask, weight)
+
+  * second block:
+    * measure the cross-products and weights
+    * add to the sparse matrix
+
+  * build full-image mask
+  * measure coord weights
+  * build order elements
+
+  * solve for source amplitudes
+  * update models
+
+  * create psSparseBorder to solve matrix equations which have a large
+    sparse region, with a completely filled border of a smaller number
+    of rows.
+
+  * trying to modify psphotEnsemble to (1) be more general (not just
+    PSFs) and (2) fit for the sky offset.
+
+    - how is the radius for each source set?
+    
+
+2006.11.11
+
+  I am trying to define a complete set of tests to validate the
+  measurement of the photometry compared with the simulated images.
+  Part of my confusion is controlling exactly which corrections are
+  applied to the output magnitudes.  Here is the complete list:
+
+  psfMag_out = psfMag_meas + ApTrend(x,y,0.0,0.0)
+  apMag_out = apMag_meas + growth(radius,refRadius) + f(skyBias,skySat)
+
+  these corrections are modified by the following config settings:
+
+  IGNORE_GROWTH (sets all growth magnitudes to the fitMag value,
+  effectively turning off the growth correction)
+
+  APTREND : set the level of correction
+    NONE     : no correction is fitted
+    CONSTANT : just a single constant (applied to get psfMag_out)
+    SKYBIAS  : the skybias term is fitted (constant applied to psfMag,
+               slope applied apMag)
+  
+2006.11.08
+
+  I have been testing the pmSourceMagnitudes interpolation of the
+  source position when measuing the aperture magnitudes.  I built a
+  test suite for pmSourceMagnitudes and pmGrowthCurve.  even with
+  bicubic interpolation, there are noticable errors for small
+  apertures (ie, small compared to the PSF sigma).  I used a gaussian
+  PSF to make the tests.  it seems that, if the radius is less than 2
+  sigma or so, the interpolated aperture mag is in error by as much as
+  12mmag.  The size of this effect must depend on the smoothing
+  algorithm, the shape of the PSF, the size of the PSF relative to the
+  pixels, and the size of the subpixel interpolation.  I believe the
+  effect is a result of the smoothing introduced by the interpolation:
+  the smoothing pushes the flux in the inner portions of the PSF down
+  and enhances the flux in the outer portions.  thus, there is
+  actually a turn over in the error: at very large values, the error
+  is very small because the aperture contains all of the flux, and the
+  interpolation has little effect.  as the radius shrinks, the error
+  grows (in the sense of M_raw - M_interpolated; ie, the flux in the
+  interpolated source is too large).  around 2-3sigma, the error turns
+  over and becomes negative (too little flux in the interpolated
+  source). 
+
+  The sigmas I used for these tests (1.0, 1.5, 2.0 pix) are
+  appropriate for the range we expect to see from PS1, or any
+  well-sampled detector.  Thus, even though the effect is probably
+  less important for poorer seeing, we have to be careful to avoid it
+  in our Pan-STARRS analysis.
+
+  Currently, the minimum aperture is defined only by the config
+  variable PSF_FIT_PAD, which really implies a padding.  My proposal
+  is to require the minimum aperture to be a number of sigma (an
+  additional parameter).  Then, the formula for the radius would be
+  something like:
+
+  MAX (modelRadiusPSF (PSF_FIT_NSIGMA), PSF_FIT_RADIUS_NSIG) +
+  PSF_FIT_PAD
+
+  with PSF_FIT_RADIUS_NSIG (choose a better name!) set to something
+  like 2 or 3.
+
+2006.11.06
+
+  I have added the aperture interpolation as a component of the
+  pmSourceMagnitudes function.  
+
+  I am trying to make things a bit more organized, with more
+  consistent APIs. 
+
+  * changed pmPSFtry to use the pmSourceMagnitudes API
+  * changed pmPSFtry to use new pmSource entries for each try (saving
+  * the pmModels generated)
+
+  other notes:
+  * moved psphotGrowthCurve -> pmGrowthCurveGenerate 
+
+  things I know need to be added / fixed:
+  * measure bicube fit to peak from smoothed image
+  * use psphotEnsemble fit to re-measure normalization?
+  * extended object statistical measures 
+
+2006.11.02
+
+  Looking into the growth curve problem.  RHL sent his recipe for
+  correcting to true circular apertures.  I've done a test fix of
+  interpolating the stellar image to the desired center.  it looks
+  like bilinear interpolation is insufficient (about 8% error for 2
+  pixel radius aperture).  Unless I've got the formula wrong, the
+  effective smoothing of the gaussian is moving flux from the center
+  out and making small apertures in error that depends on the offset.
+  Bicubic could be enough to clean this up?  
+
+  I think the current code may need to be careful modified to include
+  the interpolation for every aperture measurement.  there are paired
+  'pmModelAdd' and pmModelSub steps to add and remove the model flux.
+  I need to be careful about how the mask affects these steps.
+
+  I also need to be careful in interpreting the test results: since
+  the simulated images create objects by filling in a pixel and then
+  smoothing, all objects have centroids which are exactly 0.5 pixel
+  offsets.  These all then have a fixed growth correction error, which
+  fortunately happens to be substantial (because of a 0.5 pixel offset
+  in the growth curve reference object!)
+
+  I need to double check the 0.5 pixel error issues.  There are
+  problems with the pslib version of psImageShift and
+  psImageInterpolate (or at least inconsistencies).  the pmModelAdd
+  and Sub functions and the centroid functions are probably also
+  inconsistent.
+
+2006.11.01
+
+  Trying to understand low-level errors in psphot using simulated
+  data.  the main issue I am concerned with is the bias RHL is seeing
+  in SDSS-psphot comparisons.  But there are other issues that are
+  creeping in and probably point at logical errors in the code.
+
+  * in pmPSFtry, I measure the first pass on the aperture residual (to
+    choose between PSF model options, if that is being tested).  This
+    analysis currently uses a polynomial fit (1st order) for the offset
+    and the sky bias (using r^2/flux as an independent variable).
+
+    Notes:
+
+    - is the psf-fit error distribution reasonable?  perhaps a little
+      on the high side: at 10000 DN, noise is 0.0138, should be
+      0.0107.
+
+    - scatter of ap-fit is surprisingly high.  M_input - M_fit has a
+      median chisq of 2 (mean of 5), while M_input - M_ap has a median
+      chisq of 125 (mean of 380).  This is using a radius of 15.  It
+      cleans up hugely when I go to a radius of 7 and even more so
+      with radius = 4. (In this case, the PSF sigma is 1.5, so 4 is
+      well matched and 7 is only slightly large).
+
+      * make the initial PSF fitting radius and aperture radius
+	  different?  
+      * make the initial PSF fitting radius a function of the measured
+	  PSF sigma?
+
+    - I have been fitting ap-fit mag without weights; the resulting
+      ap-fit errors are highly over-estimated since the ap-fit scatter
+      is too high.  I have added code to carry the weights (error from
+      the fit only at the moment, ie, not the ap error as well).  This
+      is good, but not as important if the radius is chosen well above.
+
+  * a bias in the psphotEnsemble results depending on the sky level
+    but not in the PSF model fits?  turning on/off CONST_PHOT_WT has
+    an equivalent effect, but is quite small.  why is one sensitive to
+    the sky and the other is not?  
+
+  * the error measured by psphotEnsemble was wrong (and inconsistent
+    for a given weighting situation): fixed the def of the error.
+
+  * still trying to understand the corrections being made.  running a
+    few tests:
+
+    keeping PSF_FIT_RADIUS at 4.0
+
+    t1 : no growth correction,    constant weights
+    t2 :    growth correction,    constant weights
+    t3 : no growth correction, no constant weights
+    t4 :    growth correction, no constant weights
+    t5 :    growth correction,    constant weights, constant ap (4)
+    t6 :    growth correction,    constant weights, integer radius
+    t7 :    growth correction,    constant weights, integer radius
+    (for t7, I've fixed the bug that the growth correction include the
+    min radius).
+
+  * psphotGrowthCurve is messed up : the measurement does not correct
+    for the actual object center, and at small radii this is an
+    important source of noise.
+
+2006.06.28
+
+  I am adding a few minor features.  First up is the ability to break
+  the processing at interesting stages.  These will be:
+
+  - PEAKS: after sources are generated, but before moments are
+    measured
+  - MOMENTS: after moments are measured, before the PSF is measured
+  - PSFMODEL: after the psf model is measured, before sources are
+    fitted in bulk
+  - ENSEMBLE: after the linear ensemble fitting
+  - DEFAULT: complete processing
+
+2006.04.22
+
+  I have updated psphot to work with the cycle 11 release of psLib and
+  psModules.  At this point, the IfA group is now working off of the
+  CVS head rather than from a separate IfA branch of psLib and
+  psModules.  The cycle 11 release of psLib needed a few updates to
+  fix minor problems.  The psModules code needed significant work to
+  fix discrepancies.  The new version of psphot has also been leak
+  checked, at least for basic configurations.  This new version of
+  psphot should be used with the new camera configuration system,
+  which can be found in the ipp/config directory.  Here are the
+  necessary tags:
+
+  psphot: psphot_dev_11_0
+  psModules: rel11_ifa_pre0
+  psLib: rel11_ifa_0
+
+2006.04.16
+
+  Examining some of the throughput issues.  the flux measurement speed
+  is strongly dependent on the interation of the function step size:
+
+  dz = 0.01 : 3.24s for 4400 objects
+  dz = 0.1  : 0.44s for 4400 objects
+
+  there is an increased error in the result with the larger step size,
+  but it is smaller than the phot error.  Still, we could do better
+  with a smarter integration function.
+
+  Including the aperture photometry on all sources, but not the
+  pixWeight, the source magnitudes takes 4.8 sec for 4400 objects.
+
+  Adding the pixWeight increases the time to 7.5 sec for 4400 objects
+
+2006.02.12
+
+  Last week, I made a new tag (psphot_dev_08) after finishing the work
+  to clean memory leaks and to modify psphot to work with Paul's
+  ppImage infrastructure.  
+
+  I have added a new background model function which generates a
+  median map based on Robert's suggestion of a) half-step windows and
+  b) linear interpolation.
+
+  Now, I am working to remove the constant sky level concepts and make
+  every relevant test use the signal/noise ratio as the trigger.  This
+  has impacts on the peak detections, the threshold for blends, and
+  the threshold for choosing pixels in the fitting routines.
+
+2005.12.23
+
+  Improvements in psphot as of psphot_dev_07
+
+  - updated to work with psModule release 9.0
+  - moved model test functions into psphot as optional mode
+  - added pmSourceFitSet : simultaneous model group fitting
+  - added bicubic interpolation to tweak up the peak coordinates
+  - added aperture residual analysis after complete object model & subtraction
+  - added 3D (x,y,rflux) aperture residual fitting
+  - added blend vs blob fitting (BlendFit)
+  - added ensemble PSF fitting, with sparse matrix inversion
+  - improved the flagging / marking of model results
+  - better abstraction for setting fit parameters
+  - various low-level fixes
+  - added PSF output option
+  - added break points
+  - added more optional output formats
+  - fixed FITS table output
+
+  Things still to be added to psphot:
+
+  - better initial background model (NxM median grid?)
+  - include local background model in EnsemblePSF Fit (use sparse matrix)
+  - 2nd pass (after object subtraction) to smooth and detect faint objects
+  - re-add background to object-subtracted image
+  - cleanup function names
+  - push more functions into psLib / psModule
+  - fix extended / galaxy models to improve stability
+  - add mode with input PSF
+  - add mode with input fixed sources (PS_SOURCE_FIXED)
+  - cleanup memory leaks
+
+2005.11.25
+
+  I've updated psphot to work with the current psLib v8, though a
+  number of psLib fixes were needed.  These are pushed under
+  psLib:eam_rel8_b2.  The psphot code which works with that version of
+  psLib is tagged psphot_dev_04.
+
+  I'm working on converting psphot to work with the current release of
+  psModules, which basically includes all of the object functions. 
+
+  - change comments are relative to psModules (vs psphot versions)
+
+  psEllipse.c : OK
+  psEllipse.h : OK
+  pmModelGroup.c : OK (missing TGAUSS entry)
+  pmModelGroup.h : OK (adds many comments)
+
+  pmPSFtry.c : a bunch of formatting weirdness (try-> got line breaks
+	       added pmPSFtryMetric_Alt
+	       fixed up usage of psVectorClipFitPolynomial1D  
+	       fixed up usage of psPolynomial..Alloc  
+
+  pmPSFtry.h : OK (Added extensive comments)
+
+  pmPSF.c : fixed up usage of psPolynomial..Alloc
+
+  pmPSF.h : OK (Added extensive comments)
+
+  pmObjects.c : cleaned up some formatting,
+		fixed usage of psImageCountPixelMask
+
+  pmObjects.h : added apMag and fitMag to pmSource
+		dropped PS_MODEL_name defines
+
+  psLibUtils.[ch] should be dropped from psModules
+  psModulesUtils.[ch] should be dropped from psModules
+
+  Makefile.am : dropped psModulesUtils.[ch], psLibUtils.[ch]
+
+  - I have successfully tested psphot with the psModules.v8 code
+    (eam_rel8_b1 tag).  I only needed to make a few modes in the
+    psModules code.  
+
+2005.11.16
+
+  I have made some fixes to make psphot work with pslib rel8_0.  I
+  need to merge psphot with the rel8_0 version of the psMinimize and
+  pmObjects code.  
+
+2005.09.06
+
+  I have built a working version of PSPhot using my own copy of the
+  pmObjects.[ch] files.  I need to minimize the difference between the
+  v0.7.0, v0.5.0, and my own version of pmObjects.c
+
+  additions to psLib based on the new version:
+  - extended the minimization to include parameter limits
+
+  changes v0.5.0 / v0.7.0 / EAM
+  - changed all ps* to pm*
+  - fixed naming for local static functions (eg modelFree not p_psModelFree)
+  - dropped all hard-coded model names (vs v0.7.0)
+  - using current psMemSetDellocator name
+  - added noise image to pmSource
+  - converted old asserts to new asserts (PS_.._CHECK..)
+  - add return from error in pmFindImagePeaks
+  - allow isItInThisRegion to have NULL region (true)
+  - fixed subImages to correspond to region definition (end is
+    exclusive) in LocalSky and SetPixelCircle
+  - dropped old, redundant code in pmSourceLocalSky
+  - fixed pmSourceMoments use of mask (0 is valid, not 1)
+  - added pmSourceMoments validity tests (numPixels, Sum, centroid shift)
+  - added correction for Sxy (XY/Sum - x*y)
+  - added pmSourcePSFClump function (and pmPSFClump structure)
+  - allow missing sources in pmSourcePSFClump (not all peaks yield
+    sources)
+  - moved pmSourceRoughClass metadata lookups out of inner loop
+  - added saturated pixel test using pmImageCountPixelMask
+  - various redefinitions of initial classes
+  - dropped old, redundant code in pmSourceSetPixelsCircle
+  - mask image set to type psU8 (was psF32)
+  - abstract object model functions for pmSourceModelGuess
+  - converted evalModel to a public function psModelEval, modified params
+  - pmSourceFitModel operates on a specified model
+  - pmSourceFitModel had option to apply/ignore PSF information
+  - pmSourceFitModel calculates yErr
+  - pmSourceFitModel calculates the covariance matrix
+  - added paramMask to pmSourceFitModel 
+  - pmSourceFitModel_EAM uses covar to carry in beta and param
+    limits...
+  - pmSourceFitModel requires solution to stay within image
+  - pmSourceFitModel calculates and saves chisq, nIter, nDOF
+  - pmSourceFitModel uses GaussNewtonDelta for frozen params
+  - added mask to pmSourceAdd, pmSourceSub
+  - model abstractions in pmSourceAdd, pmSourceSub
+  - fixed 'center' option in pmSourceAdd/Sub
+
+  - pmPSF params should be F64
+
+  lingering concerns:
+  - Polynomials and related functions are STILL messed up.  this is a
+    major priority!!!
+  - pmSourceFitModel calculates sqrt(var), psMinimize calculates sq(sqrt(var))
+  - does psImageAlloc zero the image?  if so, drop init routine
+    from pmSourcePSFClump
+  - pmSourceRoughClass uses RDNOISE, GAIN to calculate SN: should use
+    the source->noise image? part of pmSourceMoments?
+  - pmSourceMoments_EAM uses macro for checkRegion
+  - pmObjects_EAM.c uses some hardcoded mask values (LocalSky)
+  - why does LocalSky_EAM not need subImageMask->col0 = subImage->col0??
+  - psGetRowVectorFromImage should be in psLib (in psLibUtils)
+  - pmSourceSetPixelsCircle and pmSourceLocalSky probably could use
+    the psImageMaskRegion, etc functions.
+  - pmSourceSetPixelsCircle uses a hard-coded mask value
+  - define a mask registration process??
+  - pmSourceContour is using the model flux, not the image flux
+
+2005.09.05
+
+  I have a working version of PSPhot which handles PSF and galaxy
+  models.  I am tagging the module with an alpha version number. this
+  version compiles with psLib-0.5.0, at least my version of it.  My
+  current goal is to make it work with the current psLib head (nearly
+  0.7.0) with a mimimum of _EAM versions of functions.
+
+  the tag for the working version is dev_01
+
+2005.06.04
+
+  progress on psphot has moved along well.  At this point, the process
+  loads the image, finds the peaks, determines a best PSF model,
+  identifies objects which are consistent with that model, and
+  attempts to fit a galaxy model to the objects which are not
+  consistent with the model. 
+
+  a variety of minor issues remain, as well as some major issues. 
+
+  - use a proper noise image to keep the fits honest after other
+    objects have been subtracted.  
+
+  - define the masks in terms of a single mask image.  this image
+    could be provided initially by the user.   
+
+  - add noise enhancement (couple with mask marking subtraction)
+
+  - 
+
+2005.04.12
+
+- psPeak, psSource, etc: should be pmPeak, pmSource, etc
+- pmCullPeaks: should be pmCullImagePeaks
+
+- does pmSourceMoments need an image argument?
+
+- psLibInit / p_psTimeInit
+
+  we should not have to know about astronomy time concepts (ie, have a
+  timeConfig file) in order to use the time functions for basic work.
+  This means having a PS_TIME_UNIX which just works with the UNIX
+  clock and avoids all initialization issues.
+
+- psImageStats is much too slow for basic median
+  - this function does a complete sort on all pixel values.  we need
+  to use the histogram method (spec a different type of median)
+
+- psImageStats, ROBUST_MEDIAN fails
+- there are errors in psVectorStats for ROBUST MEAN, STDEV (& median?)
+  * this is ill-defined.  revise the ADD: too sensitive to the
+  binning, smoothing scales 
+
+- pmFindImagePeaks is not finding any peaks:
+  typo in interior row section: see bug 359
+  after this fix: PASS
+
+* p_psGetRowVectorFromImage : PASS
+* psGetRowVectorFromImage : PASS 
+- both have about the same time (~15 us +/- 5us) for 2048 pix on alala
+
+* pmFindVectorPeaks : PASS (returned row data in data.U32)
+
+- # include <string.h> : needed in psMetadata.c
+
+* MyListAddPeak / pmPeakAlloc are inconsistent wrt col,row vs x,y
+
+- why does psMetadataItemAllocV allocate a string of length
+  MAX_STRING_LENGTH for the comment, then use strncpy?
+
+  should be:
+
+    // set metadata item comment
+    if (comment == NULL) {
+        // Per SDRS, null isn't allowed, must use "" instead
+        metadataItem->comment = psStringCopy ("");
+    } else {
+        metadataItem->comment = psStringCopy (comment);
+    }
+
+psMetadataAdd -> psMetadataAddItem is all confused:
+ - we end up with two psMetadataItemAlloc calls, when only one should
+   be used
+ - psMetadataItemAlloc was doing the wrong thing if the incoming data
+   was NULL
+ - I think the logic in this block is wrong as well.  
+
+ * I fixed this in psMetadata.c
+
+2005.04.14 : 
+
+ - psImageStats is still much too slow
+
+ - pmObjects : changed to using psArray to carry peaks / sources,
+   rather than psList
+
+ - added function pmPeaksSubset to replace pmCullPeaks
+
+ - pmSourceLocalSky: added min/max fncs to force subimage to stay in
+   image
+
+ - Sx, Sy can go negative: have forced limit to 0,0
+
+ - pmSourceRoughClass: implemented sigmaX, sigmaY search for stellar
+   clump (uses pmComparePeakDescend)
+
+   - the search ignores the 0,0 pixel
+
+   - pmSourceRoughClass uses all detected peaks.  allow an exclusion
+     for peak > max, peak < min?
+
+   - need to push clump stats on the metadata
+
+   - the source classes are assigned so they are mutually exclusive 
+
+   - change PS_SOURCE_OTHER to STAR? 
+
+ - pmSourceSetPixelCircle:
+
+   - name should be pmSourceSetPixelsCircle (geometry could be an
+     option...)
+
+   - I cleaned the defined region to match the convensions of
+     pmSourceLocalSky
+
+   - truncate and force subimage to lie on image
+
+   - CheckRadius2 seems like an inefficient coding
+
+   - valid pixels have mask value of 1 (inverse of other funcs)
+
+ - pmSourceFitModel:
+
+   - num iterations is much too large
+
+   - 
+
+mrq:
+
+ - allocate alpha, beta arrays
+ - mrq2dcof (pars, alpha, beta)
+ - make guess pars', alpha', beta'
+ - mrq2dcof (pars', alpha', beta')
+ - if (chisq < old chisq), keep new guess
+
+
+2005.04.19:
+
+- psMinimizeLMChi2 has some serious problems.  I re-wrote it starting
+  from the mrqmin example in ohana.   after a variety little bugs, it
+  seems to work quite well.  the program 'modeltest.c' runs two
+  different gaussians through psMinimizeLMChi2, and gets the right
+  answer quite quickly.  The first pass at the implementation had a
+  variety of problems.  to avoid any worry about errors in the
+  psMatrix code, I implemented psGaussJordan, basing the code on the
+  elixir gaussj.c code.  this worked fine, and let me find the errors
+  elsewhere in the code. the LUD version now works just the same as
+  the GaussJ version.
+
+
+2005.04.21
+
+- psMinimizeLMChi2 does an excellent job now in most cases.  I am able
+  to run pmSourceFitModel on the PSF stars quite well.  I added in the
+  (nearly) correct errors (actually, just using sqrt(N), limiting to
+  N>=1).  I see one problem object, which is quite faint, and does not
+  converge well: I get very large values for x,y, Sx, Sy, etc.  I need
+  to think about constraining these fits from running way off scale.
+
+  fit:   0.123879 sec for 277 stars (gauss)
+  fit:   0.155940 sec for 277 stars (pgauss) (!)
+  fit:   0.153292 sec for 277 stars
+  (0.5 msec per star)
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/outline.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/outline.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/outline.txt	(revision 27622)
@@ -0,0 +1,56 @@
+
+Here is a summary outline of the steps taken by psphotReadout:
+
+* setup (choose recipe, readout, etc)
+
+* create mask and weight images if needed
+
+* construct a background model image and subtract it
+
+* generate the significance image (smoothed by small Gaussian)
+
+* find peaks and associated footprints
+
+* create sources for each peak and measure moments
+  - scan over several window sigma values and choose an optimal window size
+  - at this stage, the moments are aimed at identifying psf-like objects
+
+* identify blended sources by proximity and valley depth
+
+* crude source classification guess based on moments & saturated pixels
+  - major goal is to identify the psf-stars
+  - allows for 2D variations in the psf 
+
+* use the selected PSF candidate stars to generate a PSF model	
+  - fits PSF model parameters as a function of position
+  - uses psfMag - apMag to choose order of 2D  (minimize systematic error in this value)
+  - generates a PSF residual image & optional 2D variations
+
+* fit all detected sources to the psf model (linear fit for normalization only)
+
+* high-quality source classification
+  - uses Mxx, Myy, psfMag - moment->Sum (equiv to apMags) 
+  * should the extended / psf cut be a function of galactic latitude?
+  * should the cosmic ray / psf cut be a function of galactic latitude?
+
+* non-linear fit for all brighter sources to single psf, double psf, or extended source model
+  - sources identified as extended above are fitted with extended source model
+  * are group / clumps of sources being fitted in the best way?
+  * is the psf-model ap mag being measured in an appropriate-size aperture?
+  * is the criterion for choosing between 2 psfs and extended source OK?
+  * is the test for double psf OK? (uses moments to guess starting positions)
+  
+* subtract, re-detect, measure faint sources (PSF-only)
+
+* optional extended source aperture-like measurements (petrosian, etc)
+
+* optional extended source non-linear fits (Sersic, etc)
+
+* measure psfMag-apMag correcion (2D)
+  * convert analysis to use systematic error measurement 
+
+* generate magnitudes
+
+* measure detection efficiency
+
+* output
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/output.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/output.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/output.txt	(revision 27622)
@@ -0,0 +1,15 @@
+
+we have several output formats:
+  - TEXT : separate ASCII tables for PSF, EXT, MNT, and NULL
+  - SX : sextractor-style ASCII file
+  - OBJ : dophot-style ASCII file
+  - CMP : old-style elixir format (header + text)
+  - CMF : fits-table
+
+we have several options for organization:
+  - single chip/readout : outroot.ext
+  - split chips : outroot.NN.ext
+  - mef chips : outroot.ext (extensions with names)
+
+  * only CMF will work with MEF model
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/psfmodel.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/psfmodel.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/psfmodel.txt	(revision 27622)
@@ -0,0 +1,62 @@
+
+2007.09.25
+
+  The PSF model has parameters which vary across the image.  This 2D
+  is can be represented as either a polynomial (ordinary or chebychev)
+  or as an image map.  The user requests the maximum X & Y scale
+  (PSF.TREND.NX & PSF.TREND.NY) for the variations, and
+  psphotChoosePSF / pmPSFtry attempt to find the best choice for the
+  scale.  For the polynomial representations, NX and NY are the order
+  of the variation.  For the image map, NX and NY are the size of the
+  image map.
+
+2007.09.21
+
+  there are three places where we can choose to use errors in the fits or not:
+
+  * non-linear fitting of the models to the pixel flux distribution (poissonErrorsPhotLMM)
+  * linear fitting of the models to the pixel flux distribution (poissonErrorsPhotLin)
+  * fitting of the 2D variations in the psf parameters (poissonErrorsParams)
+  * fitting of the 2D variations in the aperture residuals
+
+2007.09.20
+
+  I am upgrading the PSF model to allow the parameter variation to be
+  modeled with pmTrend2D instead of just polynomials.  I am making a
+  list of places to modify the code:
+
+pmPSFAlloc : need a method beyong psfTrendMask to carry in the psf
+options
+
+pmPSF_ModelToFit : no need to change these
+
+update pmPSFBuildSimple to set the parameters of the pmTrend, which
+ever is used.
+
+pmPSFtry.c: some significant re-work!
+
+
+
+pmPSF_IO : need new functions to save / load the trend (psImages)
+
+2006.10.27
+
+  I have been working to fix the PSF modeling in psphot.  The PSF
+  model consists of a flux model for an individual object using an
+  analytical model with a number of parameters.  For a collection of
+  PSF objects, the variation of the parameters as a function of
+  position are then themselves fitted with a model.  The PSF model for
+  a single object consists of a radial profile with a functional form
+  f(z) where a given value of z defines an elliptical coutour of the
+  form z = \frac{x^2}{2\sigma_x^2} + \frac{y^2}{2\sigma_y^2} +
+  \sigma_{xy}xy.
+
+
+
+  The term \sigma_{xy} is difficult to model as a simple function of x
+  and y (eg, a low-order polynomial).  
+
+  A better model can be constructed for
+  \frac{\sigma_{xy}}{(\sigma_x^{-2} + \sigma_y^{-2})^2}, which varies
+  like a^2 \sin 2\theta 
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/psphot.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/psphot.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/psphot.txt	(revision 27622)
@@ -0,0 +1,160 @@
+
+TBD priorities:
+
+- test psImageSmooth / remove psImageSmooth_EAM
+- unify pmSourceLocalSky / pmSourceLocalSky_EAM
+- unify pmSourceMoments_EAM / pmSourceMoments_EAM
+- merge with image data hierarchy stuff
+- better name for psPSF_Test
+
+Defined APIs:
+
+top-level psphot functions : these do not need to be in psModules
+  psMetadata  *psphotArguments ();
+  psImageData *psphotSetup ();
+  psStats     *psphotImageStats ();
+  psArray     *pmPeaksSigmaLimit ();
+  psArray     *psphotSourceStats ();
+  pmPSF       *psphotChoosePSF ();
+  bool         psphotApplyPSF ();
+  bool         psphotFitGalaxies ();
+  void         psphotOutput ();
+
+psphot-specific functions
+  bool         psphotMarkPSF ();
+  bool         psphotSubtractPSF ();
+  int          psphotSortBySN ();
+  int          psphotSaveImage ();
+
+** add the following entries to the psLibSDRS
+
+psLib extra utilities
+  bool         psTimerStart ();		- already in SDRS
+  void         psTimerFree (); 		- already in SDRS
+  bool         psTimerClear ();		- add to psLibSDRS
+  psF64        psTimerMark (); 		- already in SDRS
+  psS32        psLogArguments ();	- now: psArgumentVerbosity in SDRS
+  psS32        psTraceArguments ();	- now: psArgumentVerbosity in SDRS
+  int          psArgumentGet ();	- already in SDRS
+  int          psArgumentRemove ();	- already in SDRS
+  psVector    *psVectorCreate ();	- add to psLibSDRS
+  int          psImageCountPixelMask ();- add to psLibSDRS
+  psVector    *psGetRowVectorFromImage(); - add to psLibSDRS?
+  bool         p_psVectorPrintRow ();     - add to psLibSDRS?
+
+// basic image functions
+  bool         psImageInit ();		- already in SDRS
+  void         psImageSmooth_EAM ();    - test psLib version and drop
+
+// psLine functions
+  psLine      *psLineAlloc ();		- add to psLibSDRS
+  bool         psLineInit (); 		- add to psLibSDRS
+  bool         psLineAdd ();  		- add to psLibSDRS
+
+** compare with current implementation / update psLib
+   we need to define a mechanism to carry the beta values and the
+   parameter limits
+
+// minimize 
+  psBool       p_psMinLM_GuessABP_EAM ();
+  psBool       psMinimizeLMChi2_EAM();
+  psF64        p_psMinLM_dLinear ();  -- not included in pslib.h files
+
+** already in psLib : some need to be cleaned up by MHPCC
+
+// polynomial functions
+  psF64           Polynomial2DEval();
+  psImage        *psBuildSums2D();
+  psPolynomial2D *VectorFitPolynomial2DOrd_EAM();
+  psPolynomial2D *Polynomial2DAlloc();
+  void            psPolynomial2DDump ();
+  psPolynomial2D *RobustFit2D_nomask();
+  psPolynomial2D *RobustFit2D();
+  psVector       *Polynomial2DEvalVector();
+
+  psVector       *psBuildSums1D();
+  void            psPolynomial1DDump ();
+  psF64           Polynomial1DEval_EAM();
+  psVector       *Polynomial1DEvalVector_EAM();
+  psPolynomial1D *Polynomial1DAlloc();
+  psPolynomial1D *VectorFitPolynomial1DOrd_EAM();
+
+** make sure these are all in the psModules SDRS:
+
+pmObjects.h:
+  pmMoments           *pmMomentsAlloc();
+  pmModel             *pmModelAlloc();
+  pmSource            *pmSourceAlloc();
+  psVector            *pmFindVectorPeaks()
+  psArray             *pmFindImagePeaks()
+  psList              *pmCullPeaks()
+  pmSource            *pmSourceLocalSky()
+  bool                 pmSourceMoments()
+  pmPSFClump           pmSourcePSFClump()
+  bool                 pmSourceRoughClass()
+  bool                 pmSourceSetPixelsCircle()
+  pmModel             *pmSourceModelGuess()
+  psArray             *pmSourceContour()
+  bool                 pmSourceFitModel()
+  bool                 pmSourceAddModel()
+  bool                 pmSourceSubModel()
+  int                  pmModelParameterCount ();
+  char                *pmModelGetType ();
+  pmModelType          pmModelSetType ();
+  pmModelFunc          pmModelFunc_GetFunction ();
+  pmModelFlux          pmModelFlux_GetFunction ();
+  pmModelRadius        pmModelRadius_GetFunction ();
+  pmModelLimits        pmModelLimits_GetFunction ();
+  pmModelGuessFunc     pmModelGuessFunc_GetFunction ();
+  pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction ();
+  pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction ();
+
+model function abstractions:
+  typedef psMinimizeLMChi2Func pmModelFunc;
+  typedef psF64 (*pmModelFlux)(const psVector *params);
+  typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+  typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+  typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+  typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+  typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+conversions between elliptical shape representations
+  EllipseAxes  EllipseMomentsToAxes ();
+  EllipseShape EllipseAxesToShape ();
+  EllipseAxes  EllipseShapeToAxes ();
+
+output functions
+  bool         pmSourcesWriteText ();  	-- be careful about psImageData...
+  bool         pmSourcesWriteOBJ ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMP ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMF ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteSX ();    	-- be careful about psImageData...
+  int          pmSourcesDophotType ();
+  bool         pmPeaksWriteText ();
+  bool         pmMomentsWriteText ();
+  bool         pmModelWritePSFs ();
+  bool         pmModelWriteFLTs ();
+  bool         pmModelWriteNULLs ();
+
+// psf utilities
+  pmPSF       *pmPSFAlloc ();
+  pmPSF_Test  *pmPSF_TestAlloc ();
+  pmPSF_Test  *pmPSF_TestModel ();
+  bool         pmPSFFromModels ();
+  pmModel     *pmModelFromPSF ();
+  bool         pmSourcePhotometry ();
+  bool         pmPSFMetricModel ();
+
+// psModule extra utilities
+  bool         pmSourceFitModel_EAM();  -- requires updated version of psMinimize...
+  bool         pmSourceLocalSky_EAM (); -- compare with pmSourceLocalSky & choose
+  bool         pmSourceMoments_EAM();   -- compare with pmSourceMoments & choose
+  bool         pmSourceDefinePixels();  -- be careful about psImageData...
+  bool         pmModelFitStatus ();
+  int          pmSourceDophotType ();
+  psF32        pmConfigLookupF32 ();	-- merge with Paul's image container hierarchy
+
+** this needs to be confronted with the phase2 / image container hierarchy
+
+// psImageData functions
+  psImageData *psImageDataAlloc ();
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/regions.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/regions.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/regions.txt	(revision 27622)
@@ -0,0 +1,159 @@
+
+I am having some trouble with functions that refer to both subimages
+and regions.  there are inconsistencies between the coordinate
+conventions.  I am going to go through the list of image functions are
+decide if we can choose either 1) external coordinates always refer to
+parent coordinates or 2) something else!
+
+bool psImageSet(const psImage *image, int x, int y, double complex value);
+double complex psImageGet(const psImage *image, int x, int y);
+ - sdrs does not specify
+ - code uses subimage
+
+psRegion psRegionForImage(psImage *image, psRegion in);
+ - sdrs says subimage
+ - code uses subimage
+
+psImage *psFitsReadImage(psImage *out, const psFits *fits, psRegion region, int z);
+ - region here specifies a subimage of the disk image
+ 
+bool psFitsUpdateImage(psFits *fits, const psImage *input, int x0, int y0, int z);
+ - x0,y0 here refer to the disk image coordinates
+
+bool psFitsWriteImage(psFits *fits, psMetadata *header, const psImage
+*input, int depth, const char *extname);
+ - note that the output disk image is truncated to the subimage
+
+bool psFitsInsertImage(psFits *fits, psMetadata *header, const psImage *input, int depth,
+                       const char *extname, bool after);
+ - same as above
+
+psImage *psImageSubset(psImage *image, psRegion region);
+ - code uses region as coordinates in parent image
+ - sdrs does not specify
+ - XXX I think the range tests for region fail if the input is a
+   subimage
+
+psImage *psImageCopy(psImage *output, const psImage *input, psElemType type);
+ - maintains the col0,row0 values
+
+psVector *psImageRow(psVector *out, const psImage *input, int row);
+psVector *psImageCol(psVector *out, const psImage *input, int column);
+ - code uses subimage coordinates for row and column
+
+psVector *psImageSlice()
+psVector* psImageCut()
+ - code uses subimage coordinates (no correction)
+ - sdrs does not specify
+
+psImage *psImageRebin()
+ - mask and input image must currently agree
+
+psImage *psImageTransform()
+ - sdrs is unclear: I think the region defines the output image such
+ that the pixel in the input image *parent* coordinates which
+ corresponds to region.x0,y0 in turn corresponds to the output image
+ 0,0 pixel; the output image always has col0,row0 = 0,0
+
+long psImageCountPixelMask (psImage *mask, psRegion region, psMaskType value);
+ - sdrs says the region corresponds to subimage coordinates
+
+psPolynomial2D *psImageFitPolynomial(psPolynomial2D *coeffs, const psImage *input);
+psImage *psImageEvalPolynomial(psImage *input, const psPolynomial2D *coeffs);
+ - sdrs does not specify
+ - polynomial coordinates should refer to the *parent* image
+
+double complex psImagePixelInterpolate(input, x, y, ...)
+ - sdrs does not specify
+
+int psImageOverlaySection(psImage *image, const psImage *overlay, )
+ - sdrs does not specify
+
+psImage *psPixelsToMask()
+psPixels *psPixelsFromMask()
+ - sdrs does not specify
+
+void psImageMaskRegion()
+void psImageKeepRegion()
+ - sdrs does not specify
+
+void psImageMaskCircle()
+void psImageKeepCircle()
+ - sdrs does not specify
+
+pmReadout *pmSubtractSky(pmReadout *in, psPolynomial2D *poly, psImage *mask, psU8 maskVal, 
+                         int binFactor, psStats *stats, float clipSD);
+ - choice of polynomial coordinates
+
+stats *pmFringeStats (psArray *fringePoints, psImage *image, psMetadata *config);
+ - unspecified
+
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row);
+ - sdrs says col,row are in *subimage* coords
+
+psArray *pmFindImagePeaks(const psImage *image, float threshold);
+ - does not specify coordinate system
+ - code uses subimage?
+
+bool pmSourceDefinePixels()
+bool pmSourceRedefinePixels()
+ - sdrs does not specify very clearly
+
+
+
+---------
+
+functions which are not affected
+psFitsReadImage()
+psFitsUpdateImage()
+psFitsWriteImage()
+psFitsInsertImage()
+psImageCopy()  -- maintains col0,row0
+psImageRebin() -- mask and image must match
+
+functions which currently use parent coordinates (CORRECT)
+psImageSubset()  -- check on range tests
+psImageMaskRegion()
+psImageKeepRegion()
+psImageMaskCircle()
+psImageKeepCircle()
+pmSourceMoments -- (OK if input peaks are in parent coords)
+
+functions which currently use subimage coordinates (FIX)
+psImageSet()
+psImageGet()
+psRegionForImage() *fixed*
+psImageRow()
+psImageCol()
+psImageSlice()
+psImageCut()
+psImageCountPixelMask ();
+pmModelEval()
+pmFindImagePeaks() *fixed*
+pmSourceDefinePixels() *fixed by psRegionForImage fix*
+pmSourceRedefinePixels() *fixed by psRegionForImage fix*
+
+functions which are unclear (CHECK)
+psImageTransform()
+psImageFitPolynomial()
+psImageEvalPolynomial()
+psImagePixelInterpolate()
+psPixelsToMask()
+pmSubtractSky()
+pmFringeStats()
+
+
+***** one ambiguity: if we have a subimage, but we do not have the
+      parent image data (subimage does not carry dimensions of parent
+      array), then a 0 or negative upper-edge of a region cannot be
+      defined relative to the parent. we can specify it relative to
+      the subimage.  so, for example, given a subimage [32,100:32,100]
+      of a much larger image, then psRegionForImage with 0,0,0,0 as
+      input would return a region with values 32,100:32,100...
+
+
+
+
+
+*** things I've modified
+    psRegionForImage expects parent (not subimage) coordinates
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/speed.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/speed.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/speed.txt	(revision 27622)
@@ -0,0 +1,129 @@
+
+psphot -g
+psModule -g
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.983568 sec
+    System random seed value used  seed = 25904F7C7454AE25 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063085 sec
+    System random seed value used  seed = 411E5A0600A49AF3 hex
+    fit background model: 0.580152 sec
+    smooth: 1.842415 sec
+    threshold: 83.100540 DN
+    4356 peaks: 0.571587 sec
+    4230 moments: 3.965099 sec
+    SN range: 3.355829 - 1043.401001
+    identified 107 blended objects (0.121737 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.682850 sec for 300 sources
+    fit psf:   1.704141 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717999
+    select psf model: 5.402742 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.991345 (4230 objects)
+    built matrix: 1.472869 (27998 elements)
+    measure ensemble of PSFs: 4.334556
+    fit PSF models: 12.205738 sec for 4230 objects
+    replace unfitted models: 0.021473 sec (4230 objects)
+    measure aperture residuals : 0.451886 sec
+    measure full-frame aperture residual: 0.454032 sec
+    aperture residual: -0.002842 +/- 0.002729 : 0.166072 bias
+    wrote output: 4.648821 sec
+    complete psphot run: 35.334080 sec
+
+
+psphot -g
+psModule -O2
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 1.001502 sec
+    System random seed value used  seed = 5F43A63F532D4B3F hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063169 sec
+    System random seed value used  seed = F5F5FF99A6578F61 hex
+    fit background model: 0.530384 sec
+    smooth: 1.856414 sec
+    threshold: 83.100540 DN
+    4365 peaks: 0.237292 sec
+    4239 moments: 4.087611 sec
+    SN range: 3.356469 - 1043.400879
+    identified 107 blended objects (0.123372 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.741444 sec for 300 sources
+    fit psf:   1.676478 sec for 300 sources
+    fit stats: 0.000883 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717985
+    select psf model: 5.458156 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.791613 (4239 objects)
+    built matrix: 1.267909 (28101 elements)
+    measure ensemble of PSFs: 3.551364
+    fit PSF models: 11.240197 sec for 4239 objects
+    replace unfitted models: 0.018051 sec (4239 objects)
+    measure aperture residuals : 0.411856 sec
+    measure full-frame aperture residual: 0.413843 sec
+    aperture residual: -0.002831 +/- 0.002717 : 0.164772 bias
+    wrote output: 4.355429 sec
+    complete psphot run: 33.126283 sec
+
+after reducing indirection in pmSourceMoments:
+    4268 moments: 3.762452 sec
+
+psphot -O2
+psModule -O2
+psLib -O2
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.484324 sec
+    System random seed value used  seed = 980050732A392EC7 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.058759 sec
+    System random seed value used  seed = 44E1A6EE77B0E9CE hex
+    fit background model: 0.395125 sec
+    smooth: 1.861776 sec
+    threshold: 83.100540 DN
+    4351 peaks: 0.234874 sec
+    4264 moments: 3.719435 sec
+    SN range: 2.023345 - 1043.401123
+    identified 124 blended objects (0.115230 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.525501 sec for 300 sources
+    fit psf:   1.595031 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717976
+    select psf model: 5.160442 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.760805 (4264 objects)
+    built matrix: 0.908674 (23703 elements)
+    measure ensemble of PSFs: 3.115425
+    fit PSF models: 10.162397 sec for 4264 objects
+    replace unfitted models: 0.017491 sec (4264 objects)
+    measure aperture residuals : 0.408242 sec
+    measure full-frame aperture residual: 0.410207 sec
+    aperture residual: -0.002908 +/- 0.002734 : 0.170692 bias
+    wrote output: 4.225560 sec
+    complete psphot run: 30.149281 sec
+
+
+    fit PSF models: 10.162397 sec for 4264 objects
+    select psf model: 5.160442 sec
+    wrote output: 4.225560 sec
+    4264 moments: 3.719435 sec
+    measure ensemble of PSFs: 3.115425 sec
+
+    other : 3.5 sec
+
+    fixed per objects : 4 msec / object (17 of 30 sec)
+
+
+** still can re-compile with -DPS_NO_TRACE
+** ASSERTS cannot be compiled out at the moment!
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/stack.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/stack.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/stack.txt	(revision 27622)
@@ -0,0 +1,120 @@
+
+20100126:
+
+  * watch out for psphotSetMomentsWindow & MOMENTS_SX_MAX,etc
+  * watch out for psphotSignificanceImage.c:,psphotEfficiency using the FWHM_MAJ from psphotChoosePSF
+    * ppSimDetections.c : SIGMA_SMOOTH
+ppSmooth/src/ppSmoothReadout.c:    psMetadataAddF32(recipe, PS_LIST_TAIL, "EFFECTIVE_AREA", PS_META_REPLACE, "Effective Area", effArea);
+ppSmooth/src/ppSmoothReadout.c:    psMetadataAddF32(recipe, PS_LIST_TAIL, "SIGNIFICANCE_SCALE_FACTOR", PS_META_REPLACE, "Signicance scale factor", factor);
+
+
+20100120 : more stack processing mods:
+
+  there are a number of data collections generated during psphotReadout:
+
+  * detections (currently a structure containing multiple arrays)
+  * sources (a psArray)
+  * psf (the psf model structure)
+  
+  * new sources vs old sources:
+    there is a sequence on the second pass in which we need to distinguish the sources
+    from the first pass from those on the second pass:
+
+    - add noise (old sources only)
+    - find detections
+    - subtract noise (old sources only)
+    - generate sources (new detections only)
+    - source classifications (new sources only)
+    - guess models (new sources only)
+    - replace sources (old sources only)
+    - merge sources (new + old -> sources)
+
+    currently we are distiguishing the old vs new based on different arrays.
+    can we use the processing flags to distinguish the these cases and carry around 
+    only a single source list?
+
+    * detections->peaks holds the most recently detected set of peaks
+      detections->oldPeaks holds the previous collection
+
+      detections->footprints holds the full set of merged footprints,
+      including assigned peaks from the old and new set.
+
+20100107 : updates for stack processing
+
+  I am building a new version of psphot that operates on a set of
+  images (eg, multi-filter or multi-epoch).  The assumptions are:
+
+  * each input image represents the same sky pixels : they are warped to a common frame.
+  * each image has been previously processed, with the background
+    subtracted (and the psf model determined?)
+  * all planes are / can be loaded into memory at once (otherwise I
+    need to add a lot of I/O layers)
+
+  psphot currently expects the pmFPAfile of interest to be available in
+  config->files with the name PSPHOT.INPUT.  To update the code for
+  stacks, I am extending this concept with the pmFPAfileSelectSingle
+  API: config->files may contain multiple PSPHOT.INPUT entries, and
+  functions which access this file / these files need to specify
+  *which* image they want.  The number of PSPHOT.INPUT entries is
+  saved in config->arguments as PSPHOT.INPUT.NUM (this can be
+  generated from the filerule PSPHOT.INPUT for extension).
+
+  As a result, we need a number of wrapper functions which loop over
+  all PSPHOT.INPUT.NUM entries and perform a particular operation on
+  one of the entries.  Here are the functions which I have modified
+  in this way (function -> child)
+
+  * psphotAddPhotcode -> psphotAddPhotcodeReadout
+    * note that the photcode is now saved on readout->analysis
+  * psphotSetMaskAndVariance -> psphotSetMaskAndVarianceReadout
+  * psphotModelBackground -> psphotModelBackgroundReadoutFileIndex
+  * psphotSubtractBackground -> psphotSubtractBackgroundReadout
+
+  side notes on 
+  
+  * psphotModelBackground vs psphotBackgroundModel : I've renamed
+    psphotBackgroundModel (used only by ppStack for now) to
+    psphotModelBackgroundReadoutNoFile.  I've also named the
+    single-readout version used by psphotModelBackground to
+    psphotModelBackgroundReadoutFileIndex.  These two functions do the
+    same thing, but psphotModelBackgroundReadoutNoFile does not save
+    a pmFPAfile on config->files
+
+
+20090606 : design notes on the multi-image photometry analysis
+
+  assumptions:
+  
+  * each input image represents the same sky pixels : they are warped to a common frame.
+
+  * each image has been previously processed, with the background subtracted and the psf model determined
+
+  basic outline:
+
+  * load images, masks, variance : for now, we should load all images.  in the future, we might be able to split this into segments.  
+
+  * option: model and subtract background for each image (should not be needed)
+
+  * option: smooth each image with a psf or fraction (not needed if stack smooths too much?)
+
+  * generate the significance (chisq) image: X_i = sum(f_i^2 / var_i)
+
+  * perform the peak detection on X_i
+
+  * for each of the input images:
+
+    * generate the significance image S_i = f_i^2 / var_i 
+    
+    * perform the peak detection on S_i
+
+  * merge the list of peaks
+
+  * generate the footprints
+
+  * linear fit to the peaks with the set of images (psf for each image)
+
+
+  design issues:
+  
+  * pmSource represents the analysis for an object on a single image
+  * extended pmSourceSet to group connected pmSources on multiple images?
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/timing.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/timing.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/timing.txt	(revision 27622)
@@ -0,0 +1,84 @@
+
+                                                         -- threadable?
+                                                         |
+  0.000673 sec NPEAK      0.00 msec / item (505)       - ? Find SatStars
+  0.231828 sec NPIX       0.00 msec / item (PIX)       - ? Replace Background
+  0.349591 sec NPIX       0.00 msec / item (2048*4096) - ? Background Model
+  0.609246 sec NPIX       0.00 msec / item (2048*4096) - ? Find Peaks
+  0.733840 sec NPIX       0.00 msec / item (2048*4096) - ? Find Peaks
+  6.076478 sec NPIX       0.00 msec / item (2048*4096) - ? Smooth Images
+  7.683749 sec NPIX       0.00 msec / item (2048*4096) - ? Smooth Image
+  0.003003 sec NPEAK      0.01 msec / item (505)       - ? Source Size
+  0.003934 sec NPEAK      0.01 msec / item (??)        - ? Source Sizes
+  0.019364 sec NPEAK      0.04 msec / item (505)       - ? Find Blends
+  0.045397 sec NPEAK      0.09 msec / item (??)        - ? Replaced Flux
+  0.046370 sec NPEAK      0.09 msec / item (505)       - ? Add Noise
+  0.046680 sec NPEAK      0.09 msec / item (505)       - ? Sub Noise
+  0.050980 sec NPEAK      0.10 msec / item (505)       - ? Replaced Flux
+  2.785612 sec NFOOT      0.27 msec / item (10455)     - ? Find Footprints
+  0.322163 sec NPEAK      0.64 msec / item (505)       - ? Rough Class
+  0.163793 sec NPEAK      0.66 msec / item (??)        - ? Rough Class
+  0.820221 sec NFOOT      1.40 msec / item (587)       - ? Find Footprints
+  2.311492 sec NPEAK      4.58 msec / item (??)        - ? Built Models
+  2.413531 sec NPEAK      4.78 msec / item (505)       - ? Build Models
+  1.635674 sec NPEAK      6.54 msec / item (??)        - ? Measure Moments
+  3.547435 sec NPEAK      7.02 msec / item (505)       - ? Measure Moments
+  8.118224 sec NSRCS      7.81 msec / item (1040)      - ? ApTrend
+  4.165990 sec NPEAK      8.25 msec / item (505)       - ? Fit PSF (linear)
+  4.294968 sec NPEAK      8.50 msec / item (505)       - ? Fit PSF (linear)
+  4.638916 sec NPEAK      9.19 msec / item (??)        - ? Fit PSF (linear)
+154.599482 sec NFOOT     14.79 msec / item (10455)     - ? Cull Footprints
+  1.259951 sec NPSF      17.75 msec / item (71)        - ? Fit PSF (non-linear)
+ 22.009468 sec NFOOT     37.49 msec / item (587)       - ? Cull Footprints: 
+ 15.069461 sec NPEAK     38.34 msec / item (393)       - ? Fit PSF (non-linear) + EXT (27) 
+  2.938324 sec NPSF      41.38 msec / item (71)        - ? Fit EXT (non-linear)
+  2.723019 sec NPSF      53.39 msec / item (51)        - ? PSF Residuals
+
+244.837692 sec    complete psphot run: 
+
+Note:
+
+* PSF Residuals is the most expensive per item, but will always be
+  performed on a limited number of items (typically < 300 PSF stars).
+  The maximum impact is thus limited.
+
+* The non-linear fitting is (not surprisingly) very expensive per
+  item, but again will only be performed on a limited subset (S/N >
+  20?).
+
+* The Cull Footprints is quite expensive per item, and has the serious
+  problem that it must be performed on all peaks.  This will dominate
+  all processing time unless we can address it.
+
+* The linear fitting in this example seems to surprisingly expensive
+  per item, but I think it is not quite accurate.  
+
+* ApTrend is only measured on a subset of the sources (S/N > XX, max
+  total number)
+
+* Measure Moments and Build Models are potentially a substantial drain
+  since these (probably) should be applied to all objects.  Moments is
+  currently being limited to brighter objects.  
+
+Thread strategies:
+
+
+* tests with wbb4.031117_0154.052.fits test -chip 7 : 
+
+* baseline: 
+    grow   : 18.313876 sec
+    merged : 18.772070 sec
+    total  : 26.958080 sec (dt = 8.18)
+
+* upgrade to remove psArrayRemove:
+    grow   : 18.327212 sec
+    merged : 18.778043 sec
+    total  : 25.553486 sec (dt = 6.78; ddt = 1.4)
+
+* recycle idImage:
+    grow   : 18.383393 sec
+    merged : 18.848875 sec 
+    total  : 24.721451 sec (dt = 5.88; ddt = 2.3)
+
+(no real difference??)
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v2.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v2.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v2.txt	(revision 27622)
@@ -0,0 +1,94 @@
+#             msec                   - multithread?
+#             item                   |
+15.558560 sec 37.31    NBRT   417  : y  non-linear fits
+ 0.000736 sec  0.00    NPEAK  550  : ?  satstar blends
+ 0.005947 sec  0.00    NPEAK 1315  : ?  source sizes
+ 0.076172 sec  0.01    NPEAK 7636  : ?  set footprint ids (NPIX?)
+ 0.003383 sec  0.01    NPEAK  550  : ?  source sizes
+ 0.019354 sec  0.04    NPEAK  550  : ?  other blends
+ 0.050572 sec  0.08    NPEAK  627  : ?  set footprint ids
+ 0.049778 sec  0.09    NPEAK  550  : ?  replaced models
+ 0.051736 sec  0.09    NPEAK  550  : ?  add noise
+ 0.051929 sec  0.09    NPEAK  550  : ?  sub noise
+ 0.055094 sec  0.10    NPEAK  550  : ?  replaced models
+ 0.152204 sec  0.20    NPEAK  765  : ?  rough class
+ 0.8      sec  0.26    NPEAK 3100  : ?  merge footprints
+ 0.268983 sec  0.49    NPEAK  550  : ?  rough class
+ 0.7      sec  1.25    NPEAK  560  : ?  cull peaks
+ 2.092846 sec  2.74    NPEAK  765  : ?  moments
+ 8.9      sec  3.05    NPEAK 2914  : ?  cull peaks
+ 3.339909 sec  4.37    NPEAK  765  : ?  built models
+ 2.572153 sec  4.68    NPEAK  550  : ?  build models
+ 3.816792 sec  6.94    NPEAK  550  : ?  moments
+ 9.337548 sec  7.10    NPEAK 1315  : ?  measure magnitudes :  for 1315 objects (643 with apertures)
+ 4.301656 sec  7.82    NPEAK  550  : ?  linear fit
+ 4.429642 sec  8.05    NPEAK  550  : ?  linear fit
+ 4.947403 sec  9.00    NPEAK  550  : ?  linear fit 
+ 0.2      sec  0.02    NPIX 8300 k : ?  found grown footprints: 
+ 0.205680 sec  0.02    NPIX 8300 k : ?  find footprints
+ 0.211664 sec  0.03    NPIX 8300 k : ?  replace background
+ 0.348357 sec  0.04    NPIX 8300 k : ?  subtracted background
+ 0.598805 sec  0.07    NPIX 8300 k : ?  find peaks
+ 0.599952 sec  0.07    NPIX 8300 k : ?  find peaks 
+ 0.723508 sec  0.09    NPIX 8300 k : ?  found footprints: 
+ 1.4      sec  0.17    NPIX 8300 k : ?  found grown footprints: 
+ 0.809106 sec 12.64    NPSF    64  : ?  psf fit
+ 2.155031 sec 34.21    NPSF    63  : ?  full fit
+ 2.592620 sec 74.07    NPSF    35  : ?  psf residuals
+ 5.931090 sec  0.71   sNPIX 8300 k : ?  smoothed signficance
+ 7.780419 sec  0.94   sNPIX 8300 k : ?  smoothed signficance
+12.947970 sec  1.56   sNPIX 8300 k : ?  convolved with grow disc: 
+14.525486 sec  1.75   sNPIX 8300 k : ?  convolved with grow disc: 
+
+Notes:
+
+Note:
+
+* PSF Residuals is the most expensive per item, but will always be
+  performed on a limited number of items (typically < 300 PSF stars).
+  The maximum impact is thus limited.
+
+* The non-linear fitting is (not surprisingly) very expensive per
+  item, but again will only be performed on a limited subset (S/N >
+  20?).
+
+* the linear fits are the next most costly, and will dominate the
+  timing since all sources must be so fit, and multiple times.
+
+* measure magnitudes is expensive, but only runs once
+
+* measure moments is expensive, but only runs once (and/or on a bright subset?)
+
+Adding it up:
+
+GPC1 : 10^11 det for 60*5000* 60 chips --> 5500 det / chip
+     : 23 megapix
+
+* cost per object:
+  
+  56.5 msec -> 310 sec for detection analysis (on kawelu, not optimized)
+
+* cost for bright detections
+
+  ~ 35 sec (for 1000 bright sources)
+
+* cost per megapixel:
+
+  5.5 sec
+
+  -> 126 sec for GPC1
+
+* other overhead (psf construction, background, smoothing)
+
+  -> 20 sec
+
+** total : 490 sec (kawelu, not optimized, 1.0GHz!)
+
+** ~ 220 sec optimized 
+** ~ 100 sec on 2.3GHz machines
+(probably there are other gains on the real hardware: memory bandwidth, ?)
+
+assuming 100sec is all we get, we need ~1.5 cores per chip to keep up at 60sec / exposure (average)
+
+120 cores for ppImage (chip)
+
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v3.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v3.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v3.txt	(revision 27622)
@@ -0,0 +1,39 @@
+0.000408 sec    found 0 satstar blend peaks, leaving 551 sources: 
+0.002058 sec    measure source sizes for 551 sources: 
+0.003553 sec    measure source sizes for 1302 sources: 
+0.009676 sec    identified 36 blended objects: 
+0.026266 sec    replaced models for 551 objects: 
+0.026892 sec    sub noise for 551 objects: 
+0.026924 sec    add noise for 551 objects: 
+0.029050 sec    replaced models for 551 objects: 
+0.032975 sec    set footprint array IDs: 
+0.047061 sec    set footprint array IDs: 
+0.080533 sec    rough classification: 
+0.107024 sec    replace background flux : 
+0.111200 sec    found 631 footprints: 
+0.132417 sec    rough classification: 
+0.182467 sec    subtracted background model: 
+0.2      sec    cull peaks
+0.297289 sec    2676 peaks: 
+0.3      sec    rest of foot
+0.300492 sec    849 peaks: 
+0.364908 sec    found 7613 footprints: 
+0.393265 sec    measure ensemble of PSFs: 
+0.405241 sec    build median image: 
+0.454711 sec    measure ensemble of PSFs: 
+0.680027 sec    measure ensemble of PSFs: 
+0.900696 sec    751 sources, 290 moments, 9 failed: 
+1.1 sec    rest of footpr
+1.147541 sec    built models for 551 objects: 
+1.289976 sec    generate residuals for 35 objects: 
+1.456877 sec    built models for 751 objects: 
+1.5      sec    select psf model (ex resid)
+1.661166 sec    551 sources, 551 moments, 8 failed: 
+2.988601 sec    built smoothed signficance image: 
+3.677652 sec    built smoothed signficance image: 
+4.354904 sec    measure magnitudes :  for 1302 objects (634 with apertures)
+5.8 sec    cull peaks
+6.594287 sec    convolved with grow disc: 
+7.413455 sec    convolved with grow disc: 
+7.616990 sec    fit models:  for 417 objects (354 psf, 31 ext, 32 failed, 134 skipped)
+55.393567 sec    complete psphot run: 
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v4.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v4.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/timing.v4.txt	(revision 27622)
@@ -0,0 +1,105 @@
+
+timing on ipp008 *unoptimized* :
+
+06 :  0.003753 sec : psphotDeblendSatstars             
+22 :  0.005017 sec : psphotRoughClass                  
+08 :  0.026426 sec : psphotRoughClass                  
+07 :  0.038862 sec : psphotBasicDeblend                
+24 :  0.215222 sec : psphotReplaceAllSources           
+16 :  0.217380 sec : psphotAddNoise                    
+20 :  0.217596 sec : psphotSubNoise                    
+14 :  0.233694 sec : psphotReplaceAllSources           
+21 :  0.366264 sec : psphotSourceStats                 
+29 :  0.435839 sec : psphotSkyReplace                  
+23 :  0.572896 sec : psphotGuessModels                 
+01 :  0.650438 sec : psphotSubtractBackground          
+03 :  0.711178 sec : psphotFindDetections (peaks)      
+18 :  0.735544 sec : psphotFindDetections (peaks)      
+12 :  0.775521 sec : psphotSourceSize                  
+26 :  0.777268 sec : psphotSourceSize                  
+10 :  1.154189 sec : psphotGuessModels                 
+00 :  1.589681 sec : psphotModelBackground             
+11 :  1.724959 sec : psphotFitSourcesLinear            
+15 :  1.853397 sec : psphotFitSourcesLinear            
+25 :  2.392153 sec : psphotFitSourcesLinear            
+04 :  2.949543 sec : psphotFindDetections (footprints) 
+27 :  3.238548 sec : psphotApResid                     
+05 :  3.339873 sec : psphotSourceStats                 
+19 :  6.059952 sec : psphotFindDetections (footprints) 
+28 :  6.948404 sec : psphotMagnitudes                  
+13 :  8.305355 sec : psphotBlendFit                    
+09 : 11.317836 sec : psphotChoosePSF                   
+17 : 16.353613 sec : psphotFindDetections (smooth)     
+02 : 16.857492 sec : psphotFindDetections (smooth)     
+
+complete psphot run: 95.722628 sec
+
+timing on ipp008 *optimized* :
+
+06 :  0.001866 sec : psphotDeblendSatstars             
+22 :  0.003328 sec : psphotRoughClass                  
+08 :  0.016906 sec : psphotRoughClass                  
+07 :  0.020168 sec : psphotBasicDeblend                
+24 :  0.068499 sec : psphotReplaceAllSources           
+16 :  0.069682 sec : psphotAddNoise                    
+20 :  0.072236 sec : psphotSubNoise                    
+14 :  0.087402 sec : psphotReplaceAllSources           
+29 :  0.093756 sec : psphotSkyReplace                  
+21 :  0.151939 sec : psphotSourceStats                 
+03 :  0.203476 sec : psphotFindDetections (peaks)      
+18 :  0.210160 sec : psphotFindDetections (peaks)      
+01 :  0.242636 sec : psphotSubtractBackground          
+23 :  0.366264 sec : psphotGuessModels                 
+11 :  0.506939 sec : psphotFitSourcesLinear            
+15 :  0.539799 sec : psphotFitSourcesLinear            
+12 :  0.542919 sec : psphotSourceSize                  
+26 :  0.550523 sec : psphotSourceSize                  
+25 :  0.724011 sec : psphotFitSourcesLinear            
+00 :  0.777303 sec : psphotModelBackground             
+10 :  0.780365 sec : psphotGuessModels                 
+05 :  1.332723 sec : psphotSourceStats                 
+27 :  1.728141 sec : psphotApResid                     
+04 :  2.009312 sec : psphotFindDetections (footprints) 
+28 :  4.153111 sec : psphotMagnitudes                  
+13 :  4.527469 sec : psphotBlendFit                    
+19 :  4.538490 sec : psphotFindDetections (footprints) 
+09 :  5.504840 sec : psphotChoosePSF                   
+02 :  7.316287 sec : psphotFindDetections (smooth)     
+17 :  7.367393 sec : psphotFindDetections (smooth)     
+
+complete psphot run: 49.315078 sec
+
+timing on ipp008 *threaded* : currently (2009.01.22, 11:48 HST) only affects psphotFindDetections (smooth)
+
+06 :  0.001843 sec : psphotDeblendSatstars             
+22 :  0.003371 sec : psphotRoughClass                  
+08 :  0.016955 sec : psphotRoughClass                  
+07 :  0.020379 sec : psphotBasicDeblend                
+16 :  0.069632 sec : psphotAddNoise                    
+20 :  0.072203 sec : psphotSubNoise                    
+24 :  0.085762 sec : psphotReplaceAllSources           
+14 :  0.087238 sec : psphotReplaceAllSources           
+29 :  0.093757 sec : psphotSkyReplace                  
+21 :  0.153800 sec : psphotSourceStats                 
+03 :  0.214963 sec : psphotFindDetections (peaks)      
+18 :  0.232736 sec : psphotFindDetections (peaks)      
+01 :  0.248905 sec : psphotSubtractBackground          
+23 :  0.458357 sec : psphotGuessModels                 
+26 :  0.510125 sec : psphotSourceSize                  
+11 :  0.513049 sec : psphotFitSourcesLinear            
+15 :  0.538855 sec : psphotFitSourcesLinear            
+12 :  0.654045 sec : psphotSourceSize                  
+00 :  0.774769 sec : psphotModelBackground             
+25 :  1.024339 sec : psphotFitSourcesLinear            
+05 :  1.329678 sec : psphotSourceStats                 
+10 :  1.347421 sec : psphotGuessModels                 
+04 :  2.043960 sec : psphotFindDetections (footprints) 
+27 :  2.075074 sec : psphotApResid                     
+02 :  2.824503 sec : psphotFindDetections (smooth) ** threaded **    
+17 :  2.986548 sec : psphotFindDetections (smooth) ** threaded **    
+28 :  4.184633 sec : psphotMagnitudes                  
+19 :  4.323182 sec : psphotFindDetections (footprints) 
+09 :  5.611547 sec : psphotChoosePSF                   
+13 :  5.620031 sec : psphotBlendFit                    
+
+complete psphot run: 41.861923 sec
Index: /branches/eam_branches/stackphot.20100406/psphot/doc/versions.txt
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/doc/versions.txt	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/doc/versions.txt	(revision 27622)
@@ -0,0 +1,49 @@
+
+2006.02.02
+  I'm tagging psphot after I updated it to work with the readouts
+  using the structures being used for ppImage.  In this release, I
+  have also moved to autoconf for psphot, and the psphotReadout
+  portion is being defined as a library against which the other
+  elements are linked.  It should be possible to use this library with
+  ppImage, but I have not yet tested that.  
+
+  link psphot_dev_08 against psLib:eam_rel9_b2 and
+  psModule:eam_rel9_b2.
+
+2006.01.18
+  the tag below (psphot_dev_07) was moved to a later release.  It
+  should be used with psLib:eam_rel9_b1 from 2006/1/18 and
+  psModule:eam_rel9_p0.  
+
+2005.12.23
+  psphot_dev_07 should be compiled against psLib:eam_rel9_b1 and
+  psModule:eam_rel9_b1.  This version now handles the ensemble PSF
+  fitting, detection of blended sources, fitting of 'blends' (groups
+  of PSFs with distinct be 'blended' peaks) and 'blobs' (unresolved
+  extended objects, with a test for double PSF vs extended source
+  model.
+
+2005.11.25
+
+  psphot_dev_05 should be compiled against psLib tag eam_rel8_b2 and
+  psModules tag eam_rel8_b1.  this version removes all _EAM versions
+  of psLib and psModules code.  It now uses the psModules version of
+  the object code.
+
+  psphot_dev_04 should be compiled against psLib tag eam_rel8_b2 this
+  version removes all _EAM versions of psLib code, so that it is now
+  consistent with psLib.v8 (fixes are pushed into the eam_rel8_b2
+  branch).
+
+2005.11.16
+
+  psphot_dev_03 should be compiled against psLib tag eam_rel8_b1, a
+  branch starting from psLib rel8_0.  I needed to make some minor
+  fixes in psLib to work successfully with psLib rel8_0.
+
+  psphot_dev_02 was getting segfaults from a bug added into psLib in
+  rel8_0 in p_psVectorClippedStats.  I also fixed some errors arising
+  from changing types (argument to psMetadataParseConfig).
+
+
+
Index: /branches/eam_branches/stackphot.20100406/psphot/psphot-config.in
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/psphot-config.in	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/psphot-config.in	(revision 27622)
@@ -0,0 +1,87 @@
+#! /bin/sh
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+top_srcdir=@ABS_SRCDIR@
+
+usage()
+{
+    cat <<EOF
+Usage: psphot-config [OPTION]
+
+Known values for OPTION are:
+
+  --prefix		print psphot installation prefix
+  --libs		print library linking information
+  --cflags		print pre-processor and compiler flags
+  --build-libs		print library linking information to the build (non-installed) version
+  --build-cflags	print pre-processor and compiler flags to the build (non-installed) version
+  --help		display this help and exit
+  --version		output version information
+
+EOF
+
+    exit $1
+}
+
+if test $# -eq 0; then
+    usage 1
+fi
+
+cflags=false
+libs=false
+
+while test $# -gt 0; do
+    case "$1" in
+    -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+    *) optarg= ;;
+    esac
+
+    case "$1" in
+    --prefix=*)
+	prefix=$optarg
+	;;
+
+    --prefix)
+	echo $prefix
+	;;
+
+    --version)
+	echo @VERSION@
+	exit 0
+	;;
+
+    --help)
+	usage 0
+	;;
+
+    --cflags)
+       	echo -I${includedir} @PSPHOT_CFLAGS@
+       	;;
+
+    --libs)
+       	echo -L${libdir} -lpsphot @PSPHOT_LIBS@
+       	;;
+
+    --build-cflags)
+       	echo @SRCINC@ @PSPHOT_CFLAGS@
+       	;;
+
+    --build-libs)
+       	echo -L@ABS_SRCDIR@/src/.libs -lpsphot @PSPHOT_LIBS@
+       	;;
+
+    --deps)
+       	echo @PSPHOT_LIBS@
+       	;;
+    *)
+	usage
+	exit 1
+	;;
+    esac
+    shift
+done
+
+exit 0
Index: /branches/eam_branches/stackphot.20100406/psphot/psphot.pc.in
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/psphot.pc.in	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/psphot.pc.in	(revision 27622)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libpsphot
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lpsphot
+Libs.private: @PSPHOT_LIBS@
+Cflags: -I${includedir}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/Makefile.am
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/Makefile.am	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/Makefile.am	(revision 27622)
@@ -0,0 +1,215 @@
+lib_LTLIBRARIES = libpsphot.la
+
+if HAVE_SVNVERSION
+PSPHOT_VERSION=`$(SVNVERSION) ..`
+else
+PSPHOT_VERSION="UNKNOWN"
+endif
+
+if HAVE_SVN
+PSPHOT_BRANCH=`$(SVN) info .. | $(SED) -n -e '/URL:/ h' -e '/Repository Root:/ { x; H; x; s|Repository Root: \(.*\)\nURL: \1\(.*\)|\2| ; s|^/|| ; s|/[a-zA-Z]*/src.*|| ; p }'`
+PSPHOT_SOURCE=`$(SVN) info | $(SED) -n -e 's/Repository UUID: // p'`
+else
+PSPHOT_BRANCH="UNKNOWN"
+PSPHOT_SOURCE="UNKNOWN"
+endif
+
+# Force recompilation of psphotVersion.c, since it gets the version information
+psphotVersion.c: psphotVersionDefinitions.h
+psphotVersionDefinitions.h: psphotVersionDefinitions.h.in FORCE
+	-$(RM) psphotVersionDefinitions.h
+	$(SED) -e "s|@PSPHOT_VERSION@|\"$(PSPHOT_VERSION)\"|" -e "s|@PSPHOT_BRANCH@|\"$(PSPHOT_BRANCH)\"|" -e "s|@PSPHOT_SOURCE@|\"$(PSPHOT_SOURCE)\"|" psphotVersionDefinitions.h.in > psphotVersionDefinitions.h
+FORCE: ;
+
+libpsphot_la_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+libpsphot_la_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+
+bin_PROGRAMS = psphot psphotForced psphotMakePSF
+# bin_PROGRAMS = psphotPetrosianStudy psphotTest psphotMomentsStudy 
+
+psphot_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psphot_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psphot_LDADD = libpsphot.la
+
+psphotForced_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psphotForced_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psphotForced_LDADD = libpsphot.la
+
+psphotMakePSF_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psphotMakePSF_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psphotMakePSF_LDADD = libpsphot.la
+
+# psphotMomentsStudy_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+# psphotMomentsStudy_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+# psphotMomentsStudy_LDADD = libpsphot.la
+
+# psphotPetrosianStudy_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+# psphotPetrosianStudy_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+# psphotPetrosianStudy_LDADD = libpsphot.la
+
+# psphotTest_CFLAGS = $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+# psphotTest_LDFLAGS = $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+# psphotTest_LDADD = libpsphot.la
+
+# standard psphot for generic photometry
+psphot_SOURCES = \
+        psphot.c                \
+	psphotArguments.c	\
+	psphotParseCamera.c     \
+	psphotImageLoop.c	\
+	psphotMosaicChip.c	\
+	psphotCleanup.c
+
+# forced photometry of specified positions given a specified psf
+psphotForced_SOURCES = \
+        psphotForced.c             \
+	psphotForcedArguments.c	   \
+	psphotForcedImageLoop.c	   \
+	psphotForcedReadout.c	   \
+	psphotParseCamera.c        \
+	psphotMosaicChip.c	   \
+	psphotCleanup.c
+
+# a psphot-variant that simply generates the PSF model
+psphotMakePSF_SOURCES = \
+        psphotMakePSF.c            \
+	psphotMakePSFArguments.c   \
+	psphotMakePSFImageLoop.c   \
+	psphotMakePSFReadout.c	   \
+	psphotParseCamera.c        \
+	psphotMosaicChip.c	   \
+	psphotCleanup.c
+
+# # psphot analysis of the detectability of specified positions
+# psphotDetect_SOURCES =            \
+#         psphotDetect.c            \
+# 	psphotDetectArguments.c	  \
+# 	psphotDetectParseCamera.c \
+# 	psphotDetectImageLoop.c	  \
+# 	psphotDetectReadout.c	  \
+# 	psphotMosaicChip.c	  \
+# 	psphotCleanup.c
+
+# psphotTest_SOURCES = \
+#         psphotTest.c
+# 
+# psphotMomentsStudy_SOURCES = \
+#         psphotMomentsStudy.c
+# 
+# psphotPetrosianStudy_SOURCES = \
+#         psphotPetrosianStudy.c
+
+libpsphot_la_SOURCES = \
+	psphotSetThreads.c             \
+	psphotErrorCodes.c	       \
+	psphotImageQuality.c           \
+	psphotVisual.c                 \
+	psphotCullPeaks.c	       \
+	psphotVersion.c		       \
+	psphotModelGroupInit.c	       \
+	psphotMaskReadout.c	       \
+	psphotDefineFiles.c	       \
+	psphotReadout.c		       \
+	psphotReadoutFindPSF.c	       \
+	psphotReadoutKnownSources.c    \
+	psphotReadoutMinimal.c	       \
+	psphotModelBackground.c	       \
+	psphotSubtractBackground.c     \
+	psphotFindDetections.c	       \
+	psphotFindPeaks.c	       \
+	psphotFindFootprints.c	       \
+	psphotSignificanceImage.c      \
+	psphotSourceStats.c	       \
+	psphotRoughClass.c	       \
+	psphotBasicDeblend.c	       \
+	psphotChoosePSF.c	       \
+	psphotGuessModels.c            \
+	psphotFitSourcesLinear.c       \
+	psphotBlendFit.c	       \
+	psphotReplaceUnfit.c	       \
+	psphotApResid.c		       \
+	psphotMakeGrowthCurve.c	       \
+	psphotMagnitudes.c	       \
+	psphotSetMaskBits.c	       \
+	psphotSkyReplace.c	       \
+	psphotEvalPSF.c		       \
+	psphotEvalFLT.c		       \
+	psphotSourceFits.c	       \
+	psphotRadiusChecks.c	       \
+	psphotOutput.c		       \
+	psphotFakeSources.c	       \
+	psphotModelWithPSF.c           \
+	psphotExtendedSourceAnalysis.c \
+	psphotExtendedSourceFits.c     \
+	psphotKernelFromPSF.c	       \
+	psphotPSFConvModel.c	       \
+	psphotFitSet.c		       \
+	psphotSourceFreePixels.c       \
+	psphotSummaryPlots.c           \
+	psphotMergeSources.c	       \
+	psphotLoadPSF.c	               \
+	psphotLoadSRCTEXT.c            \
+	psphotReadoutCleanup.c	       \
+	psphotSourcePlots.c	       \
+	psphotRadialPlot.c	       \
+	psphotDeblendSatstars.c	       \
+	psphotMosaicSubimage.c	       \
+	psphotMakeResiduals.c	       \
+	psphotSourceSize.c	       \
+	psphotDiagnosticPlots.c	       \
+	psphotMakeFluxScale.c	       \
+	psphotCheckStarDistribution.c  \
+	psphotThreadTools.c  	       \
+	psphotAddNoise.c               \
+	psphotRadialProfile.c	       \
+        psphotRadialProfileByAngles.c  \
+	psphotRadiiFromProfiles.c      \
+        psphotEllipticalContour.c      \
+        psphotEllipticalProfile.c      \
+	psphotPetrosian.c	       \
+        psphotPetrosianRadialBins.c    \
+        psphotPetrosianStats.c         \
+	psphotEfficiency.c
+
+# XXX need to fix this for the new apis
+#	psphotModelTest.c	       
+
+# re-instate these
+#	psphotIsophotal.c	       \
+#	psphotAnnuli.c		       \
+#	psphotKron.c		       \
+#       psphotPetrosianVisual.c        \
+#
+
+# test versions
+#       psphotPetrosianProfile.c       \
+#       psphotPetrosianAnalysis.c      \
+#
+
+include_HEADERS = \
+	psphot.h \
+	psphotErrorCodes.h
+
+noinst_HEADERS = \
+	psphotInternal.h \
+	psphotStandAlone.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+# Error codes.
+BUILT_SOURCES = psphotErrorCodes.h psphotErrorCodes.c psphotVersionDefinitions.h
+CLEANFILES = psphotErrorCodes.h psphotErrorCodes.c
+EXTRA_DIST = psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h.in \
+	models/pmModel_STRAIL.c \
+	models/pmModel_TEST1.c
+
+psphotErrorCodes.h : psphotErrorCodes.dat psphotErrorCodes.h.in
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.h
+
+psphotErrorCodes.c : psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.c
Index: /branches/eam_branches/stackphot.20100406/psphot/src/models/pmModel_STRAIL.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/models/pmModel_STRAIL.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/models/pmModel_STRAIL.c	(revision 27622)
@@ -0,0 +1,620 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = length;
+    params->data.F32[8] = theta;
+*****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_STRAIL
+# define PM_MODEL_FLUX            pmModelFlux_STRAIL
+# define PM_MODEL_GUESS           pmModelGuess_STRAIL
+# define PM_MODEL_LIMITS          pmModelLimits_STRAIL
+# define PM_MODEL_RADIUS          pmModelRadius_STRAIL
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_STRAIL
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_STRAIL
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_STRAIL
+
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 trailLength = PAR[7];
+    psF32 theta = PAR[8];
+
+    psF32 x0 = PAR[2];  //streak center
+    psF32 y0 = PAR[3];  //streak center
+
+    //S values (1/sigma for x and y case, sigma for xy case)
+    psF32 sx = PAR[4];
+    psF32 sy = PAR[5];
+    psF32 sxy = PAR[6];
+
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+    psF32 sin2T=sin(2.0*theta);
+    psF32 cos2T=cos(2.0*theta);
+
+    //    printf("Trying object at %4.1f,%4.1f with length %3.1f and angle %1.3f\r", x0, y0, length, theta);
+
+    //current location relative to trail center
+    psF32 X  = x->data.F32[0] - x0;
+    psF32 Y  = x->data.F32[1] - y0;
+
+    //x' and y' location (trail-orienter coords)
+    psF32 xs = X*cosT + Y*sinT;
+    psF32 ys = -1.0*X*sinT + Y*cosT;
+
+    //initialize variables to be changed below
+    psF32 x1 = 0;
+    psF32 y1 = 0;
+    psF32 px = 0;
+    psF32 py = 0;
+    psF32 z  = 0;
+    psF32 zx = 0;
+    psF32 t  = 0;
+    psF32 tx = 0;
+    psF32 r  = 0;
+    psF32 rx = 0;
+    psF32 f  = 0;
+
+    psF32 sxrot = 0;
+    psF32 syrot = 0;
+    psF32 sxyrot = 0;
+    psF32 dsxrot = 0;
+    psF32 dsyrot = 0;
+    psF32 dsxyrot = 0;
+
+    //    psF32 Rx = 0;
+    //    psF32 Ry = 0;
+    //    psF32 Rxy = 0;
+
+
+    //calculate new S values (1/sigma) for rotated frame
+    psF32 sxrotsq = PS_SQR(cosT*sx) + PS_SQR(sinT*sy) + cosT*sinT*sxy;
+    psF32 syrotsq = PS_SQR(cosT*sy) + PS_SQR(sinT*sx) - cosT*sinT*sxy;
+
+    //    psF32 testtwo=10.1;
+    //    psF32 testone=fabsf(testtwo);
+    //    fprintf (stderr, "Test: %f is the absolute value of %f?\n",testone,testtwo);
+    if (sxrotsq<0) {
+      sxrot = sqrt(-(sxrotsq));
+      syrot = sqrt(syrotsq);
+      fprintf (stderr, "error in sxrotsq: Neg,  sxrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",sxrotsq,sx,sy,sxy,theta);
+    } else if (syrotsq<0) {
+      sxrot = sqrt(sxrotsq);
+      syrot = sqrt(-(syrotsq));
+      fprintf (stderr, "error in syrotsq: Neg,  syrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",syrotsq,sx,sy,sxy,theta);
+    } else if (sxrotsq==0){
+      sxrot = 0.01;
+      syrot = sqrt(syrotsq);
+      fprintf (stderr, "error in sxrotsq: Zero,  sxrotsq=%f \n",sxrotsq);
+    } else if (syrotsq==0) {
+      syrot = 0.01;
+      sxrot = sqrt(sxrotsq);
+      fprintf (stderr, "error in syrotsq: Zero,  syrotsq=%f \n",syrotsq);
+      //      return(0);
+    }else {
+      sxrot = sqrt(sxrotsq);
+      syrot = sqrt(syrotsq);
+    }
+
+    sxyrot = sxy*cos2T + (PS_SQR(sy) - PS_SQR(sx))*sin2T;
+
+      if (isnan(sxrot)) {
+        fprintf (stderr, "error in sxrot  syrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",syrot,sx,sy,sxy,cosT,sinT);
+      } else if (isnan(syrot)) {
+        fprintf (stderr, "error in syrot  sxrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",sxrot,sx,sy,sxy,cosT,sinT);
+      }
+
+    //calculate length of pipe (not of trail motion)
+    psF32 length = trailLength - 2.0*2.0/sxrot;
+
+
+    if (xs < length/(-2.0)) {
+      x1 = (xs+length/2.0)*cosT - ys*sinT; //Endpoint1
+      y1 = (xs+length/2.0)*sinT + ys*cosT; //Endpoint1
+      //1.6 factor comes from by-eye testing of fits, sqrt from the later squaring of it
+      //1.6 ~= phi (golden mean)...coincidence?
+      px = sxrot*x1/sqrt(1.6);
+      py = syrot*y1;
+
+      //first find out what the falloff in the x direction is...
+      zx  = 0.5*PS_SQR(px);
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0;
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0);
+
+      //...and now in the y direction
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      f  = PAR[1]*rx*r + PAR[0];
+
+    } else if (xs > length/2.0){
+      x1 = (xs-length/2.0)*cosT - ys*sinT; //Endpoint2
+      y1 = (xs-length/2.0)*sinT + ys*cosT; //Endpoint2
+      px = sxrot*x1/sqrt(1.6);
+      py = syrot*y1;
+      zx  = 0.5*PS_SQR(px);
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0;
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0);
+
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      f  = PAR[1]*rx*r + PAR[0];
+
+      if (isnan(r)) {
+        fprintf (stderr, "error in +r  t=%f z=%f\n",t,z);
+      }
+
+    } else {
+      x1 = -ys*sinT;
+      y1 = ys*cosT;
+      px = sx*x1;
+      py = sy*y1;
+      z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + sxy*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      rx = 1.0;  //so that dF/dF0 can be generalized
+      f  = PAR[1]*r + PAR[0];
+
+      if (isnan(r)) {
+        fprintf (stderr, "error in midr  t=%f z=%f\n",t,z);
+      }
+    }
+
+
+    //ok...so this is df/dPAR[X]
+    if (deriv != NULL) {
+        psF32 q = 1;
+        //stable
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r * rx;
+
+          if (isnan(deriv->data.F32[0])) {
+            fprintf (stderr, "error in deriv0\n");
+          } else if (isnan(deriv->data.F32[1])) {
+            fprintf (stderr, "error in deriv1 r=%f rx=%f\n",r,rx);
+          }
+
+        dsxrot = 2.0*PS_SQR(sy)*sinT*cosT - 2.0*PS_SQR(sx)*sinT*cosT + sxy*(PS_SQR(cosT)-PS_SQR(sinT));
+        dsyrot = 2.0*PS_SQR(sx)*sinT*cosT - 2.0*PS_SQR(sy)*sinT*cosT - sxy*(PS_SQR(cosT)-PS_SQR(sinT));
+        dsxyrot = 2.0*cos2T*(PS_SQR(sy)-PS_SQR(sx)) - 2.0*sxy*sin2T;
+
+          if (isnan(dsxrot)) {
+            fprintf (stderr, "error in dsxrot\n");
+          } else if (isnan(dsyrot)) {
+            fprintf (stderr, "error in dsyrot\n");
+          } else if (isnan(dsxyrot)) {
+            fprintf (stderr, "error in dsxyrot\n");
+          }
+
+        //initialize variables definied in the if statements
+        //      psF32 XXX=0;
+        //      psF32 YYY=0;
+
+
+        // variable over piecewise func
+        // change the endcaps to be 4th order gaussians with sxrot_fit=1.6*sxrot
+        // y' direction can ge adequately modelled by a 3rd order gaussian with syrot
+        if (xs < length/(-2.0)) {
+
+          q=PAR[1]*r*rx;
+          deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot);
+          deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot);
+          deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT+2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T+y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)+2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T+y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1+2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1+2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T+y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)+x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[7] = -q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT));
+          deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)+y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)+x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT)));
+
+          /*      if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+          */
+
+
+
+        } else if (xs > length/2.0){
+
+          q=PAR[1]*r*rx;
+          deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot);
+          deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot);
+          deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT-2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T-y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)-2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T-y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1-2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1-2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T-y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)-x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[7] = q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT));
+          deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)-y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)-x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT)));
+
+
+          /*      if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+          */
+
+        } else {
+          // this does not change from before, as the y' falloff can be modelled by the standard 3rd order gaussian
+          // note difference from a pure gaussian: q = PAR[1]*r
+          q = PAR[1]*r*r*t;
+          deriv->data.F32[2] = q*(PS_SQR(sx*sinT)*x1 - PS_SQR(sy)*y1*cosT*sinT - sxy*x1*sinT*cosT + sxy*y1*PS_SQR(sinT));
+          deriv->data.F32[3] = q*(-1*PS_SQR(sx)*x1*sinT*cosT + PS_SQR(sy*cosT)*y1 + sxy*x1*PS_SQR(cosT) - sxy*y1*sinT*cosT);
+          deriv->data.F32[4] = -q*sx*PS_SQR(x1);
+          deriv->data.F32[5] = -q*sy*PS_SQR(y1);
+          deriv->data.F32[6] = -q*x1*y1;
+          deriv->data.F32[7] = 0;
+          deriv->data.F32[8] = -q*( PS_SQR(sx)*x1*(xs*sinT - ys*cosT) + PS_SQR(sy)*y1*(xs*cosT - ys*sinT) + sxy*x1*(xs*cosT - ys*sinT) + sxy*y1*(xs*sinT - ys*cosT) );
+
+          if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+
+
+        }
+    }
+    return(f);
+}
+
+//fixed
+// XXX this needs to apply the axis ratio limits to prevent avoid solutions
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta) {
+
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+    float f1, f2, q1;
+    float q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        assert (q1 > 0);
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+          case PM_PAR_SKY:  beta_lim = 1000;   break;
+          case PM_PAR_I0:   beta_lim = 10000;    break; // too small?
+          case PM_PAR_XPOS: beta_lim = 50;     break;
+          case PM_PAR_YPOS: beta_lim = 50;     break;
+          case PM_PAR_SXX:  beta_lim = 0.5;    break;
+          case PM_PAR_SYY:  beta_lim = 0.5;    break;
+          case PM_PAR_SXY:  beta_lim = 1.0;    break;  // set this to q2?
+          case 7:           beta_lim = 10.0;     break;
+          case 8:           beta_lim = M_PI/6.0; break;
+
+          default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_min = -1000; break;
+          case PM_PAR_I0:   params_min =     0; break;
+          case PM_PAR_XPOS: params_min =  -100; break;
+          case PM_PAR_YPOS: params_min =  -100; break;
+          case PM_PAR_SXX:  params_min =   0.5; break;
+          case PM_PAR_SYY:  params_min =   0.5; break;
+          case PM_PAR_SXY:  params_min =  -5.0; break; // set this to -q2?
+          case 7:           params_min =     0;  break;
+          case 8:           params_min = -1*M_PI; break;
+
+          default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_max =   1e5; break;
+          case PM_PAR_I0:   params_max =   1e8; break;
+          case PM_PAR_XPOS: params_max =   1e4; break;
+          case PM_PAR_YPOS: params_max =   1e4; break;
+          case PM_PAR_SXX:  params_max =   100; break;
+          case PM_PAR_SYY:  params_max =   100; break;
+          case PM_PAR_SXY:  params_max =   +q2; break;
+          case 7:           params_max =   150; break;
+          case 8:           params_max =  M_PI; break;
+          default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            return false;
+        }
+        return true;
+      default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+//fixed
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF32 Rx=2./PS_SQR(PAR[4]);
+    psF32 Ry=2./PS_SQR(PAR[5]);
+    psF32 Rxy=PAR[6];
+
+
+    psF32 theta = PAR[8];
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y
+
+    psF64 A4   = Syrot*PAR[7];
+
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3) + A4;
+    // Area is equivalent to 2 pi sigma^2 + rectangle
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        f = 1.0 / (1 + z + z*z/2 + z*z*z/6);
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = params->data.F32[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// also prevent 0 returns, and just send a v small number
+// return the radius which yields the requested flux
+
+//fixed, but need to change how it is called to accomodate 2 radii
+psF64 PM_MODEL_RADIUS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1]) return (1.0);
+
+    psF32 *PAR = params->data.F32;
+    psF32 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+
+    psF32 theta = PAR[8];
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+    psF32 Rx=2./PS_SQR(PAR[4]);
+    psF32 Ry=2./PS_SQR(PAR[5]);
+    psF32 Rxy=PAR[6];
+    psF32 length=PAR[7];
+
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y
+
+    psF64 radius = 0;
+    if (flux > 0){
+      psF64 radius0 = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+      psF64 radius1 = Syrot * sqrt (2.0 * log(params->data.F32[1] / flux));
+
+      if (radius0 > radius1) {
+        radius=radius0+length/2.0;
+      } else {
+        radius=radius1+length/2.0;
+      }
+    } else {
+      radius = 1000;
+    }
+
+    if (radius < 0.01){
+      radius = 0.01;
+    }
+
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+//fixed I think...no good way of guessing as far as I can tell
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *Smoments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = Smoments->Mxx;
+    moments.y2 = Smoments->Myy;
+    moments.xy = Smoments->Mxy;
+    //sometimes these moment inputs are zero...why?
+
+    // solve the math to go from Moments To Shape
+    // limit the axis ratio to 20 (a guess)
+    axes = psEllipseMomentsToAxes(moments, 20.0);
+    shape = psEllipseAxesToShape(axes);
+
+    params[PM_PAR_SKY]  = Smoments->Sky;
+    params[PM_PAR_I0]   = peak->flux;
+    params[PM_PAR_XPOS] = peak->xf;
+    params[PM_PAR_YPOS] = peak->yf;
+
+    params[7] = 2 * axes.major;
+    params[8] = axes.theta;
+
+    if (moments.x2 == 0 || moments.y2 == 0){
+      params[4] = 2.0;
+      params[5] = 2.0;
+      params[6] = 0.0;
+    } else {
+      params[4] = 1.0 / shape.sx;
+      params[5] = 1.0 / shape.sy;
+      params[6] = shape.sxy;
+    }
+
+    //    printf("Who is NaN? momx: %4.3f  momy: %4.3f  momxy: %4.3f\n", moments.x2,moments.y2, moments.xy);
+
+    return(true);
+}
+
+//fixed
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+    out[7] = in[7];
+    out[8] = in[8];
+
+    for (int i = 4; i < 7; i++) {
+      pmTrend2D *trend = psf->params->data[i-4];
+        out[i] = pmTrend2DEval (trend, out[2], out[3]);
+    }
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+//done I think
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+    //    status &= ((dPAR[7]/PAR[7]) < 0.5);
+    //    status &= ((dPAR[8]/PAR[8]) < 0.5);
+
+    if (status) return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/stackphot.20100406/psphot/src/models/pmModel_TEST1.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/models/pmModel_TEST1.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/models/pmModel_TEST1.c	(revision 27622)
@@ -0,0 +1,322 @@
+/******************************************************************************
+ * this file defines the TEST1 source shape model.  Note that these model functions are loaded
+ * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
+
+ * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+ * PM_PAR_I0 1    - central intensity
+ * PM_PAR_XPOS 2  - X center of object
+ * PM_PAR_YPOS 3  - Y center of object
+ * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) * SigmaX)
+ * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) * SigmaY)
+ * PM_PAR_SXY 6   - X*Y term of elliptical contour
+ *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_TEST1
+# define PM_MODEL_FLUX            pmModelFlux_TEST1
+# define PM_MODEL_GUESS           pmModelGuess_TEST1
+# define PM_MODEL_LIMITS          pmModelLimits_TEST1
+# define PM_MODEL_RADIUS          pmModelRadius_TEST1
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_TEST1
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_TEST1
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_TEST1
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                    const psVector *params,
+                    const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    // XXX this is fitting sigma_x/sqrt(2), sigma_y/sqrt(2)
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    psF32 t  = 1 + z + z*z/2.0;
+    psF32 r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+        psF32 q = PAR[PM_PAR_I0]*r*r*t;
+        dPAR[PM_PAR_SKY]  = +1.0;
+        dPAR[PM_PAR_I0]   = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_SXX]  = +2.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY]  = +2.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY]  = -q*X*Y;
+    }
+    return(f);
+}
+
+// define the parameter limits
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+          case PM_PAR_SKY:  beta_lim = 1000;  break;
+          case PM_PAR_I0:   beta_lim = 3e6;   break;
+          case PM_PAR_XPOS: beta_lim = 5;     break;
+          case PM_PAR_YPOS: beta_lim = 5;     break;
+          case PM_PAR_SXX:  beta_lim = 0.5;   break;
+          case PM_PAR_SYY:  beta_lim = 0.5;   break;
+          case PM_PAR_SXY:  beta_lim = 0.5;   break;
+          default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_min = -1000; break;
+          case PM_PAR_I0:   params_min =     0; break;
+          case PM_PAR_XPOS: params_min =  -100; break;
+          case PM_PAR_YPOS: params_min =  -100; break;
+          case PM_PAR_SXX:  params_min =   0.5; break;
+          case PM_PAR_SYY:  params_min =   0.5; break;
+          case PM_PAR_SXY:  params_min =  -5.0; break;
+          default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_max =   1e5; break;
+          case PM_PAR_I0:   params_max =   1e8; break;
+          case PM_PAR_XPOS: params_max =   1e4; break;
+          case PM_PAR_YPOS: params_max =   1e4; break;
+          case PM_PAR_SXX:  params_max =   100; break;
+          case PM_PAR_SYY:  params_max =   100; break;
+          case PM_PAR_SXY:  params_max =  +5.0; break;
+          default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            return false;
+        }
+        return true;
+      default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *PAR  = model->params->data.F32;
+
+    psEllipseMoments emoments;
+    emoments.x2 = moments->Mxx;
+    emoments.y2 = moments->Myy;
+    emoments.xy = moments->Mxy;
+
+    // force the axis ratio to be < 20.0
+    psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    PAR[PM_PAR_SKY] = moments->Sky;
+    PAR[PM_PAR_I0]   = peak->flux;
+    PAR[PM_PAR_XPOS] = peak->xf;
+    PAR[PM_PAR_YPOS] = peak->yf;
+    PAR[PM_PAR_SXX] = PS_MAX(0.5, M_SQRT2*shape.sx);
+    PAR[PM_PAR_SYY] = PS_MAX(0.5, M_SQRT2*shape.sy);
+    PAR[PM_PAR_SXY] = shape.sxy;
+    PAR[PM_PAR_SXY] = 0.0;  // XXX we can get this right if we do the integral
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float norm, z;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+# define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        z += DZ;
+        f2 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[PM_PAR_I0])
+        return (1.0);
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // this estimates the radius assuming f(z) is roughly exp(-z)
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 radius = axes.major * sqrt (2.0 * log(params->data.F32[PM_PAR_I0] / flux));
+
+    if (isnan(radius)) psAbort("error in code: never return invalid radius");
+    if (radius < 0) psAbort("error in code: never return invalid radius");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the 2D model for SXY actually fits SXY / (SXX^-2 + SYY^-2); correct here
+    out[PM_PAR_SXY] = pmPSF_SXYtoModel (out);
+
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+// XXX double-check these definitions below
+// this test is invalid if the parameters are derived
+// from the PSF model
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphot.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphot.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphot.c	(revision 27622)
@@ -0,0 +1,35 @@
+# include "psphotStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();                  // register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotArguments (argc, argv);
+    assert(config);
+
+    psphotVersionPrint();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphot.h
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphot.h	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphot.h	(revision 27622)
@@ -0,0 +1,343 @@
+/* This file defines the library functions available to external programs.  It must be included
+ * by programs which are compiled against psphot functions.
+ */
+
+#ifndef PSPHOT_H
+#define PSPHOT_H
+
+#include <psmodules.h>
+#include "psphotErrorCodes.h"
+
+#define PSPHOT_RECIPE "PSPHOT" // Name of the recipe to use
+
+#define PSPHOT_RECIPE_PSF_FAKE_ALLOW "PSF.FAKE.ALLOW" // Name for recipe component permitting fake PSFs
+
+// pmPCMData : PSF Convolved Model data storage structure
+typedef struct {
+    psImage *model;
+    psArray *dmodels;
+    psImage *modelConv;
+    psArray *dmodelsConv;
+} pmPCMData;
+
+// top-level psphot functions
+const char     *psphotCVSName(void);
+psString        psphotVersion(void);
+psString        psphotSource(void);
+psString        psphotVersionLong(void);
+bool            psphotVersionHeader(psMetadata *header);
+bool            psphotVersionHeaderFull(psMetadata *header);
+void            psphotVersionPrint(void);
+
+bool            psphotModelTest (pmConfig *config, const pmFPAview *view, psMetadata *recipe);
+bool            psphotInit (void);
+bool            psphotReadout (pmConfig *config, const pmFPAview *view);
+bool            psphotReadoutFindPSF(pmConfig *config, const pmFPAview *view, psArray *inSources);
+bool            psphotReadoutKnownSources(pmConfig *config, const pmFPAview *view, psArray *inSources);
+bool            psphotReadoutMinimal(pmConfig *config, const pmFPAview *view);
+
+bool            psphotReadoutCleanup (pmConfig *config, const pmFPAview *view);
+bool            psphotReadoutCleanupReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotDefineFiles (pmConfig *config, pmFPAfile *input);
+void            psphotFilesActivate(pmConfig *config, bool state);
+
+bool            psphotSetMaskBits (pmConfig *config);
+bool            psphotSetMaskRecipe (pmConfig *config, psImageMaskType maskValue, psImageMaskType markValue);
+
+// XXX test functions
+psArray        *psphotFakeSources (void);
+bool            psphotMaskCosmicRayFootprintCheck (psArray *sources);
+
+// psphotReadout functions
+bool            psphotAddPhotcode (pmConfig *config, const pmFPAview *view);
+bool            psphotAddPhotcodeReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index);
+
+bool            psphotSetMaskAndVariance (pmConfig *config, const pmFPAview *view);
+bool            psphotSetMaskAndVarianceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotModelBackground (pmConfig *config, const pmFPAview *view);
+bool            psphotModelBackgroundReadoutFileIndex (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotSubtractBackground (pmConfig *config, const pmFPAview *view);
+bool            psphotSubtractBackgroundReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotFindDetections (pmConfig *config, const pmFPAview *view, bool firstPass);
+bool            psphotFindDetectionsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool firstPass);
+
+bool            psphotSourceStats (pmConfig *config, const pmFPAview *view, bool setWindow);
+bool            psphotSourceStatsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool setWindow);
+
+bool            psphotDeblendSatstars (pmConfig *config, const pmFPAview *view);
+bool            psphotDeblendSatstarsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotBasicDeblend (pmConfig *config, const pmFPAview *view);
+bool            psphotBasicDeblendReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotRoughClass (pmConfig *config, const pmFPAview *view);
+bool            psphotRoughClassReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+bool            psphotRoughClassRegion (int nRegion, psRegion *region, psArray *sources, psMetadata *analysis, psMetadata *recipe, const bool havePSF);
+
+bool            psphotImageQuality (pmConfig *config, const pmFPAview *view);
+bool            psphotImageQualityReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotChoosePSF (pmConfig *config, const pmFPAview *view);
+bool            psphotChoosePSFReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotGuessModels (pmConfig *config, const pmFPAview *view);
+bool            psphotGuessModelsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotMergeSources (pmConfig *config, const pmFPAview *view);
+bool            psphotMergeSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotFitSourcesLinear (pmConfig *config, const pmFPAview *view, bool final);
+bool            psphotFitSourcesLinearReadout (psMetadata *recipe, pmReadout *readout, psArray *sources, pmPSF *psf, bool final);
+
+bool            psphotSourceSize (pmConfig *config, const pmFPAview *view, bool getPSFsize);
+bool            psphotSourceSizeReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool getPSFsize);
+
+bool            psphotBlendFit (pmConfig *config, const pmFPAview *view);
+bool            psphotBlendFitReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+bool            psphotBlendFit_Threaded (psThreadJob *job);
+
+bool            psphotReplaceAllSources (pmConfig *config, const pmFPAview *view);
+bool            psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotAddNoise (pmConfig *config, const pmFPAview *view);
+bool            psphotSubNoise (pmConfig *config, const pmFPAview *view);
+bool            psphotAddOrSubNoise (pmConfig *config, const pmFPAview *view, bool add);
+bool            psphotAddOrSubNoiseReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool add);
+
+bool            psphotExtendedSourceAnalysis (pmConfig *config, const pmFPAview *view);
+bool            psphotExtendedSourceAnalysisReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotExtendedSourceFits (pmConfig *config, const pmFPAview *view);
+bool            psphotExtendedSourceFitsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotApResid (pmConfig *config, const pmFPAview *view);
+bool            psphotApResidReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotMagnitudes (pmConfig *config, const pmFPAview *view);
+bool            psphotMagnitudesReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources, pmPSF *psf);
+bool            psphotMagnitudes_Threaded (psThreadJob *job);
+
+bool            psphotEfficiency (pmConfig *config, const pmFPAview *view);
+bool            psphotEfficiencyReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe);
+
+bool            psphotPSFWeights(pmConfig *config, pmReadout *readout, const pmFPAview *view, psArray *sources);
+bool            psphotPSFWeights_Threaded (psThreadJob *job);
+
+bool            psphotSkyReplace (pmConfig *config, const pmFPAview *view);
+bool            psphotSkyReplaceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+bool            psphotSourceFreePixels (pmConfig *config, const pmFPAview *view);
+bool            psphotSourceFreePixelsReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index);
+
+// in psphotSourceStats.c:
+bool            psphotSourceStats_Threaded (psThreadJob *job);
+bool            psphotSourceStatsUpdate (psArray *sources, pmConfig *config, pmReadout *readout);
+bool            psphotSetMomentsWindow (psMetadata *recipe, psMetadata *analysis, psArray *sources);
+
+// in psphotChoosePSF.c:
+bool            psphotPSFstats (pmReadout *readout, pmPSF *psf);
+bool            psphotMomentsStats (pmReadout *readout, psArray *sources);
+
+// in psphotGuessModel.c
+bool            psphotGuessModel_Threaded (psThreadJob *job);
+
+// in psphotMergeSources.c:
+bool            psphotLoadExtSources (pmConfig *config, const pmFPAview *view);
+psArray        *psphotLoadPSFSources (pmConfig *config, const pmFPAview *view);
+bool            psphotRepairLoadedSources (pmConfig *config, const pmFPAview *view);
+bool            psphotCheckExtSources (pmConfig *config, const pmFPAview *view);
+
+// generate the detection structure for the supplied array of sources
+bool            psphotDetectionsFromSources (pmConfig *config, const pmFPAview *view, psArray *sources);
+
+// generate the detection structure for the supplied array of sources
+bool            psphotSetSourceParams (pmConfig *config, psArray *sources, pmPSF *psf);
+
+// in psphotModelBackground.c:
+// Create a background model for a readout, without saving the result as a pmFPAfile on config->files.  Otherwise identical to psphotModelBackgroundFileIndex.
+psImage        *psphotModelBackgroundReadoutNoFile(pmReadout *readout, const pmConfig *config);
+psImageBinning *psphotBackgroundBinning(const psImage *image, const pmConfig *config);
+
+// in psphotReplaceUnfit.c:
+bool            psphotRemoveAllSources (const psArray *sources, const psMetadata *recipe);
+bool            psphotReplaceUnfitSources (psArray *sources, psImageMaskType maskVal);
+
+// thread-related:
+bool            psphotSetThreads (void);
+bool            psphotChooseCellSizes (int *Cx, int *Cy, pmReadout *readout, int nThreads);
+bool            psphotCoordToCell (int *group, int *cell, float x, float y, int Cx, int Cy);
+psArray        *psphotAssignSources (int Cx, int Cy, psArray *sources);
+
+// used by psphotFindDetections
+psImage        *psphotSignificanceImage (pmReadout *readout, psMetadata *recipe, const int pass, psImageMaskType maskVal);
+psArray        *psphotFindPeaks (psImage *significance, pmReadout *readout, psMetadata *recipe, const float threshold, const int nMax);
+bool            psphotFindFootprints (pmDetections *detections, psImage *significance, pmReadout *readout, psMetadata *recipe, const float threshold, const int pass, psImageMaskType maskVal);
+psErrorCode     psphotCullPeaks(const psImage *img, const psImage *weight, const psMetadata *recipe, psArray *footprints);
+
+// in psphotApResid.c:
+pmTrend2D      *psphotApResidTrend (float *apResidSysErr, pmReadout *readout, int Nx, int Ny, psVector *xPos, psVector *yPos, psVector *apResid, psVector *dMag);
+bool            psphotApResidMags_Threaded (psThreadJob *job);
+
+// basic support functions
+void            psphotModelClassInit (void);
+bool            psphotGrowthCurve (pmReadout *readout, pmPSF *psf, bool ignore, psImageMaskType maskVal);
+
+// functions to set the correct source pixels
+bool            psphotInitRadiusPSF(const psMetadata *recipe, const psMetadata *analysis, const pmModelType type);
+
+bool            psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal);
+bool            psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal, float dR);
+bool            psphotInitRadiusEXT (psMetadata *recipe, pmModelType type);
+bool            psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal);
+float           psphotSetRadiusEXT (pmReadout *readout, pmSource *source, psImageMaskType markVal);
+
+bool            psphotDumpMoments (psMetadata *recipe, psArray *sources);
+psMetadata     *psphotDefineHeader (psMetadata *recipe);
+int             psphotSaveImage (psMetadata *header, psImage *image, char *filename);
+bool            psphotDumpConfig (pmConfig *config);
+pmReadout      *psphotSelectBackground (pmConfig *config, const pmFPAview *view);
+pmReadout      *psphotSelectBackgroundStdev (pmConfig *config, const pmFPAview *view);
+
+// PSF / DBL / EXT evaluation functions
+bool            psphotEvalPSF (pmSource *source, pmModel *model);
+bool            psphotEvalDBL (pmSource *source, pmModel *model);
+bool            psphotEvalEXT (pmSource *source, pmModel *model);
+
+//  functions to support the source fitting process
+bool            psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout);
+bool            psphotInitLimitsEXT (psMetadata *recipe);
+bool            psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf, psImageMaskType maskVal, psImageMaskType markVal);
+bool            psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf, psImageMaskType maskVal, psImageMaskType markVal);
+bool            psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf, psImageMaskType maskVal, psImageMaskType markVal);
+pmModel        *psphotFitEXT (pmReadout *readout, pmSource *source, pmModelType modelType, psImageMaskType maskVal, psImageMaskType markVal);
+psArray        *psphotFitDBL (pmReadout *readout, pmSource *source, psImageMaskType maskVal, psImageMaskType markVal);
+
+// functions to support simultaneous multi-source fitting
+bool            psphotFitSet (pmSource *oneSrc, pmModel *oneModel, char *fitset, pmSourceFitMode mode, psImageMaskType maskVal);
+
+// plotting functions (available if libkapa is installed)
+bool            psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources);
+bool            psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources);
+bool            psphotFitInit (int nThreads);
+bool            psphotFitSummary (void);
+
+bool            psphotLoadPSF (pmConfig *config, const pmFPAview *view);
+bool            psphotLoadPSFReadout (pmConfig *config, const pmFPAview *view, const char *outFilename, const char *inFilename, int index);
+
+bool            psphotSetHeaderNstars (psMetadata *recipe, psArray *sources);
+bool            psphotRadialPlot (int *kapa, const char *filename, pmSource *source);
+bool            psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotMosaicSubimage (psImage *outImage, pmSource *source, int Xo, int Yo, int DX, int DY, bool normalize);
+
+bool            psphotMakeResiduals (psArray *sources, psMetadata *recipe, pmPSF *psf, psImageMaskType maskVal);
+
+pmModel        *psphotPSFConvModel (pmReadout *readout, pmSource *source, pmModelType modelType, psImageMaskType maskVal, psImageMaskType markVal, int psfSize);
+
+psKernel       *psphotKernelFromPSF (pmSource *source, int nPix);
+
+// functions related to extended source analysis
+bool            psphotRadialProfile (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal);
+bool            psphotRadialProfilesByAngles (pmSource *source, int Nsec, float Rmax);
+float           psphotRadiusFromProfile (pmSource *source, psVector *radius, psVector *flux, float fluxMin, float fluxMax);
+bool            psphotRadiiFromProfiles (pmSource *source, float fluxMin, float fluxMax);
+bool            psphotEllipticalProfile (pmSource *source);
+bool            psphotEllipticalContour (pmSource *source);
+
+// psphotVisual functions
+bool            psphotVisualShowImage (pmReadout *readout);
+bool            psphotVisualShowBackground (pmConfig *config, const pmFPAview *view, pmReadout *readout);
+bool            psphotVisualShowSignificance (psImage *image, float min, float max);
+bool            psphotVisualShowPeaks (pmDetections *detections);
+bool            psphotVisualShowFootprints (pmDetections *detections);
+bool            psphotVisualShowMoments (psArray *sources);
+bool            psphotVisualPlotMoments (psMetadata *recipe, psMetadata *analysis, psArray *sources);
+bool            psphotVisualShowRoughClass (psArray *sources);
+bool            psphotVisualShowPSFStars (psMetadata *recipe, pmPSF *psf, psArray *sources);
+bool            psphotVisualShowSatStars (psMetadata *recipe, pmPSF *psf, psArray *sources);
+bool            psphotVisualShowPSFModel (pmReadout *readout, pmPSF *psf);
+bool            psphotVisualPlotRadialProfile (int myKapa, pmSource *source, psImageMaskType maskVal);
+bool            psphotVisualPlotRadialProfiles (psMetadata *recipe, psArray *sources);
+bool            psphotVisualShowFlags (psArray *sources);
+bool            psphotVisualShowSourceSize (pmReadout *readout, psArray *sources);
+bool            psphotVisualShowResidualImage (pmReadout *readout);
+bool            psphotVisualPlotApResid (psArray *sources, float mean, float error);
+bool            psphotVisualPlotChisq (psArray *sources);
+bool            psphotVisualPlotSourceSize (psMetadata *recipe, psMetadata *analysis, psArray *sources);
+bool            psphotVisualShowPetrosians (psArray *sources);
+bool            psphotVisualEraseOverlays (int channel, char *overlay);
+
+bool psphotPetrosian (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal);
+bool psphotPetrosianRadialBins (pmSource *source, float radiusMax, float skynoise);
+bool psphotPetrosianStats (pmSource *source);
+
+// currently disabled:
+// bool            psphotIsophotal (pmSource *source, psMetadata *recipe, psImageMaskType maskVal);
+// bool            psphotAnnuli (pmSource *source, psMetadata *recipe, psImageMaskType maskVal);
+// bool            psphotKron (pmSource *source, psMetadata *recipe, psImageMaskType maskVal);
+
+// XXX visualization functions related to radial profiles (disabled)
+// bool psphotPetrosianVisualProfileByAngle (psVector *radius, psVector *flux);
+// bool psphotPetrosianVisualProfileRadii (psVector *radius, psVector *flux, psVector *radiusBin, psVector *fluxBin, float peakFlux, float RadiusRef);
+// bool psphotPetrosianVisualEllipticalContour (pmPetrosian *petrosian);
+// bool psphotPetrosianVisualStats (psVector *radBin, psVector *fluxBin,
+//                               psVector *refRadius, psVector *meanSB,
+//                               psVector *petRatio, psVector *petRatioErr, psVector *fluxSum,
+//                               float petRadius, float ratioForRadius,
+//                               float petFlux, float radiusForFlux);
+
+// structures & functions to support psf-convolved model fitting
+
+// psf-convolved model fitting
+bool psphotModelWithPSF_LMM (
+    psMinimization *min,
+    psImage *covar,
+    psVector *params,
+    psMinConstraint *constraint,
+    pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func);
+
+psF32 psphotModelWithPSF_SetABX(
+    psImage  *alpha,
+    psVector *beta,
+    const psVector *params,
+    const psVector *paramMask,
+    pmPCMData *pcm,
+    const pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func);
+
+pmPCMData *pmPCMDataAlloc (
+    const psVector *params,
+    const psVector *paramMask,
+    pmSource *source);
+
+psImage *pmPCMDataSaveImage (pmPCMData *pcm);
+
+int psphotKapaOpen (void);
+bool psphotKapaClose (void);
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy);
+bool psphotDiagnosticPlots (const pmConfig *config, const char *name, ...);
+
+bool psphotMakeFluxScale (psImage *image, psMetadata *recipe, pmPSF *psf);
+bool psphotMakeGrowthCurve (pmReadout *readout, psMetadata *recipe, pmPSF *psf);
+bool psphotDumpPSFStars (pmReadout *readout, pmPSFtry *try, float radius, psImageMaskType maskVal, psImageMaskType markVal);
+
+bool psphotLoadSRCTEXT (pmFPA *fpa, pmConfig *config);
+
+bool psphotCheckStarDistribution (psArray *sources, psArray *stars, pmPSFOptions *options);
+int psphotSupplementStars (psArray *stars, psArray *sources, psImageBinning *binning, int ix, int iy);
+
+pmConfig *psphotForcedArguments(int argc, char **argv);
+bool psphotForcedImageLoop (pmConfig *config);
+bool psphotForcedReadout(pmConfig *config, const pmFPAview *view);
+
+pmConfig *psphotMakePSFArguments(int argc, char **argv);
+bool psphotMakePSFImageLoop (pmConfig *config);
+bool psphotMakePSFReadout(pmConfig *config, const pmFPAview *view);
+
+#endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotAddNoise.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotAddNoise.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotAddNoise.c	(revision 27622)
@@ -0,0 +1,135 @@
+# include "psphotInternal.h"
+
+bool psphotAddNoise (pmConfig *config, const pmFPAview *view) {
+    return psphotAddOrSubNoise (config, view, true);
+}
+
+bool psphotSubNoise (pmConfig *config, const pmFPAview *view) {
+    return psphotAddOrSubNoise (config, view, false);
+}
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotAddOrSubNoise (pmConfig *config, const pmFPAview *view, bool add)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotAddOrSubNoiseReadout (config, view, "PSPHOT.INPUT", i, recipe, add)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to modify noise for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotAddOrSubNoiseReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool add) {
+
+    bool status = false;
+    psEllipseShape oldshape;
+    psEllipseShape newshape;
+    psEllipseAxes axes;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+    psAssert (readout->parent, "missing cell?");
+    psAssert (readout->parent->concepts, "missing concepts?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    psTimerStart ("psphot.noise");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    psAssert (maskVal, "missing mask value?");
+
+    // increase variance by factor*(object noise):
+    // weight = flux/gain + rn^2/g^2
+    // we are adding factor*flux/gain
+
+    float FACTOR = psMetadataLookupF32 (&status, recipe, "NOISE.FACTOR");
+    PS_ASSERT (status, false);
+    float SIZE = psMetadataLookupF32 (&status, recipe, "NOISE.SIZE");
+    PS_ASSERT (status, false);
+
+    if (SIZE <= 0) {
+       return true;
+    }
+
+    float GAIN = psMetadataLookupF32(&status, readout->parent->concepts, "CELL.GAIN"); // Cell gain
+    PS_ASSERT (status, false);
+    if (isfinite(GAIN)) {
+        FACTOR /= GAIN;
+    }
+
+    // loop over all source
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip sources which were not subtracted
+	// NOTE: this bit is not modified when pmSourceOp applies to noise
+        if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) continue;
+
+        // select appropriate model
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL) continue;  // model must be defined
+
+        if (add) {
+            psTrace ("psphot", 4, "adding noise for object at %f,%f\n", model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS]);
+        } else {
+            psTrace ("psphot", 4, "remove noise for object at %f,%f\n", model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS]);
+        }
+
+        psF32 *PAR = model->params->data.F32;
+
+        // save original values
+        float oldI0  = PAR[PM_PAR_I0];
+        oldshape.sx  = PAR[PM_PAR_SXX];
+        oldshape.sy  = PAR[PM_PAR_SYY];
+        oldshape.sxy = PAR[PM_PAR_SXY];
+
+        // XXX can this be done more intelligently?
+        if (oldI0 == 0.0) continue;
+        if (!isfinite(oldI0)) continue;
+
+        // increase size and height of source
+        axes = psEllipseShapeToAxes (oldshape, 20.0);
+        axes.major *= SIZE;
+        axes.minor *= SIZE;
+        newshape = psEllipseAxesToShape (axes);
+        PAR[PM_PAR_I0]  = FACTOR*oldI0;
+        PAR[PM_PAR_SXX] = newshape.sx;
+        PAR[PM_PAR_SYY] = newshape.sy;
+        PAR[PM_PAR_SXY] = newshape.sxy;
+
+        // XXX if we use pmSourceOp, the size (and possibly Io) will not be respected
+        pmSourceOp (source, PM_MODEL_OP_FULL | PM_MODEL_OP_NOISE, add, maskVal, 0, 0);
+
+        // restore original values
+        PAR[PM_PAR_I0]  = oldI0;
+        PAR[PM_PAR_SXX] = oldshape.sx;
+        PAR[PM_PAR_SYY] = oldshape.sy;
+        PAR[PM_PAR_SXY] = oldshape.sxy;
+    }
+    if (add) {
+        psLogMsg ("psphot.noise", PS_LOG_INFO, "add noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.noise"));
+    } else {
+        psLogMsg ("psphot.noise", PS_LOG_INFO, "sub noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.noise"));
+    }
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotAnnuli.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotAnnuli.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotAnnuli.c	(revision 27622)
@@ -0,0 +1,61 @@
+# include "psphotInternal.h"
+
+bool psphotAnnuli (pmSource *source, psMetadata *recipe, psImageMaskType maskVal) {
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  bool status;
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *variance = source->extpars->profile->variance;
+  psVector *flux = source->extpars->profile->flux;
+
+  // XXX how do I define the radii?  we can put a vector in the recipe...
+  // radialBins defines the bounds or start and stop (we can skip some that way...
+  psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
+  psVector *radialBinsUpper = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
+  assert (radialBinsLower->n == radialBinsUpper->n);
+
+  psVector *fluxValues = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *fluxSquare = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *fluxVariance = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *pixelCount = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVectorInit (fluxValues, 0.0);
+  psVectorInit (fluxSquare, 0.0);
+  psVectorInit (fluxVariance, 0.0);
+  psVectorInit (pixelCount, 0.0);
+
+  // XXX this code assumes the radii are in pixels.  convert from arcsec with plate scale
+  // XXX assume the annulii above are not overlapping?  much faster...
+  // XXX this might be must faster in the reverse order: loop over annulii and use disection to
+  // skip to the start of the annulus.
+  for (int i = 0; i < flux->n; i++) {
+    for (int j = 0; j < radialBinsLower->n; j++) {
+      if (radius->data.F32[i] < radialBinsLower->data.F32[j]) continue;
+      if (radius->data.F32[i] > radialBinsUpper->data.F32[j]) continue;
+      fluxValues->data.F32[j] += flux->data.F32[i];
+      fluxSquare->data.F32[j] += PS_SQR(flux->data.F32[i]);
+      fluxVariance->data.F32[j] += variance->data.F32[i];
+      pixelCount->data.F32[j] += 1.0;
+    }
+  }
+
+  for (int j = 0; j < radialBinsLower->n; j++) {
+    fluxValues->data.F32[j] /= pixelCount->data.F32[j];
+    fluxSquare->data.F32[j] /= pixelCount->data.F32[j];
+    fluxSquare->data.F32[j] -= PS_SQR(fluxValues->data.F32[j]);
+  }
+
+  source->extpars->annuli = pmSourceAnnuliAlloc ();
+  source->extpars->annuli->flux    = fluxValues;
+  source->extpars->annuli->fluxErr = fluxVariance;
+  source->extpars->annuli->fluxVar = fluxSquare;
+
+  psFree (pixelCount);
+
+  return true;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotApResid.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotApResid.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotApResid.c	(revision 27622)
@@ -0,0 +1,488 @@
+# include "psphotInternal.h"
+
+# define SKIPSTAR(MSG) { psTrace ("psphot", 3, "invalid : %s", MSG); continue; }
+// measure the aperture residual statistics and 2D variations
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotApResid (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotApResidReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure aperture residual for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotApResidReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
+{
+    int Nfail = 0;
+    int Nskip = 0;
+    int Npsf;
+    bool status;
+    pmModel *model;
+    pmSource *source;
+
+    psTimerStart ("psphot.apresid");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping ap resid");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+	nThreads = 0;
+    }
+
+    bool measureAptrend = psMetadataLookupBool (&status, recipe, "MEASURE.APTREND");
+    if (!measureAptrend) {
+        // save nan values since these were not calculated
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   NAN);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", NAN);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", 0);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", NAN);
+        return true;
+    }
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // clipping for extreme outliers
+    // XXX this is not currently defined in the recipe
+    float MAX_AP_OFFSET = psMetadataLookupF32 (&status, recipe, "MAX_AP_OFFSET");
+
+    // options for how the photometry is calculated
+    // XXX are these sensible?
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+
+    // XXX is this still needed?  the pmTrend2D stuff should be auto-adjusting...
+    int APTREND_NSTAR_MIN = psMetadataLookupS32(&status, recipe, "APTREND.NSTAR.MIN");
+    assert (status);
+
+    // maximum order for aperture correction
+    int APTREND_ORDER_MAX = psMetadataLookupS32(&status, recipe, "APTREND.ORDER.MAX");
+    assert (status);
+
+    if (APTREND_ORDER_MAX < 1) {
+        psError(PSPHOT_ERR_CONFIG, true, "APTREND.ORDER.MAX must be 1 or more");
+        return false;
+    }
+
+    pmSourcePhotometryMode photMode = 0;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+
+    // set limits on the aperture magnitudes
+    pmSourceMagnitudesInit (recipe);
+
+    // threaded measurement of the source magnitudes
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+	psArray *cells = cellGroups->data[i];
+
+	for (int j = 0; j < cells->n; j++) {
+
+	    // allocate a job -- if threads are not defined, this just runs the job
+	    psThreadJob *job = psThreadJobAlloc ("PSPHOT_APRESID_MAGS");
+
+	    psArrayAdd(job->args, 1, cells->data[j]); // sources
+	    psArrayAdd(job->args, 1, psf);
+	    PS_ARRAY_ADD_SCALAR(job->args, photMode, PS_TYPE_S32);
+	    PS_ARRAY_ADD_SCALAR(job->args, maskVal,  PS_TYPE_IMAGE_MASK);
+	    PS_ARRAY_ADD_SCALAR(job->args, markVal,  PS_TYPE_IMAGE_MASK);
+
+	    PS_ARRAY_ADD_SCALAR(job->args, 0,        PS_TYPE_S32); // this is used as a return value for Nskip
+	    PS_ARRAY_ADD_SCALAR(job->args, 0,        PS_TYPE_S32); // this is used as a return value for Nfail
+
+	    if (!psThreadJobAddPending(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		psFree (job);
+		return false;
+	    }
+	    psFree(job);
+	}
+
+	// wait for the threads to finish and manage results
+	if (!psThreadPoolWait (false)) {
+	    psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+	    return false;
+	}
+
+	// we have only supplied one type of job, so we can assume the types here
+	psThreadJob *job = NULL;
+	while ((job = psThreadJobGetDone()) != NULL) {
+	    if (job->args->n < 1) {
+		fprintf (stderr, "error with job\n");
+	    } else {
+		psScalar *scalar = NULL;
+		scalar = job->args->data[5];
+		Nskip += scalar->data.S32;
+		scalar = job->args->data[6];
+		Nfail += scalar->data.S32;
+	    }
+	    psFree(job);
+	}
+    }
+
+    psFree (cellGroups);
+
+    // gather the stats to assess the aperture residuals
+    psVector *mag     = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *xPos    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *yPos    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *apResid = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *dMag    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    Npsf = 0;
+
+# ifdef DEBUG    
+    FILE *f = fopen ("apresid.dat", "w");
+    psAssert (f, "failed open");
+# endif
+
+    for (int i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        model = source->modelPSF;
+
+        if (source->type != PM_SOURCE_TYPE_STAR) SKIPSTAR ("NOT STAR");
+        if (source->mode &  PM_SOURCE_MODE_SATSTAR) SKIPSTAR ("SATSTAR");
+        if (source->mode &  PM_SOURCE_MODE_BLEND) SKIPSTAR ("BLEND");
+        if (source->mode &  PM_SOURCE_MODE_FAIL) SKIPSTAR ("FAIL STAR");
+        if (source->mode &  PM_SOURCE_MODE_POOR) SKIPSTAR ("POOR STAR");
+
+	if (source->mode &  PM_SOURCE_MODE_EXT_LIMIT) SKIPSTAR ("EXTENDED");
+	if (source->mode &  PM_SOURCE_MODE_CR_LIMIT) SKIPSTAR ("COSMIC RAY");
+	if (source->mode &  PM_SOURCE_MODE_DEFECT) SKIPSTAR ("DEFECT");
+	    
+        if (!isfinite(source->apMag) || !isfinite(source->psfMag)) {
+            continue;
+        }
+
+	// XXX make this user-configurable?
+	if (source->errMag > 0.01) continue;
+
+        // aperture residual for this source
+        float dap = source->apMag - source->psfMag;
+
+        // sanity clipping : if the model is very discrepant, put your expectation in the recipe
+        if ((MAX_AP_OFFSET > 0) && (fabs(dap) > MAX_AP_OFFSET)) {
+            Nfail ++;
+            psTrace ("psphot", 3, "fail : bad dap %f %f", dap, MAX_AP_OFFSET);
+            continue;
+        }
+
+# ifdef DEBUG
+	fprintf (f, "%6.1f %6.1f : %6.1f %6.1f : %8.3f %8.3f %8.3f : %f : %f %f %f : %f\n",
+		 source->peak->xf, source->peak->yf, 
+		 source->modelPSF->params->data.F32[PM_PAR_XPOS], source->modelPSF->params->data.F32[PM_PAR_YPOS], 
+		 source->psfMag, source->apMag, source->errMag,
+		 source->modelPSF->params->data.F32[PM_PAR_I0], 
+		 source->modelPSF->params->data.F32[PM_PAR_SXX], source->modelPSF->params->data.F32[PM_PAR_SXY], source->modelPSF->params->data.F32[PM_PAR_SYY], 
+		 source->modelPSF->params->data.F32[PM_PAR_7]);
+# endif
+	if (!isfinite(source->psfMag)) psAbort ("nan in psfMag");
+	if (!isfinite(source->errMag)) psAbort ("nan in errMag");
+	if (!isfinite(source->apMag)) psAbort ("nan in apMag");
+	if (!isfinite(model->params->data.F32[PM_PAR_XPOS])) psAbort ("nan in xPos");
+	if (!isfinite(model->params->data.F32[PM_PAR_YPOS])) psAbort ("nan in yPos");
+
+        psVectorAppend (mag, source->psfMag);
+        psVectorAppend (dMag,source->errMag);
+        psVectorAppend (apResid, dap);
+        psVectorAppend (xPos, model->params->data.F32[PM_PAR_XPOS]);
+        psVectorAppend (yPos, model->params->data.F32[PM_PAR_YPOS]);
+        Npsf ++;
+    }
+
+    psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "measure aperture residuals for %d objects (%d skipped, %d failed, %ld invalid)\n",
+              Npsf, Nskip, Nfail, sources->n - Npsf - Nskip - Nfail);
+
+# ifdef DEBUG
+    fclose (f);
+# endif
+
+    // XXX choose a better value here?
+    if (Npsf < APTREND_NSTAR_MIN) {
+        psWarning("Only %d valid aperture residual sources (need %d), giving up", Npsf, APTREND_NSTAR_MIN);
+	goto escape;
+    }
+
+    // this is a bit tricky, because we have two cases (MAP vs POLY), and they have a different 
+    // definition for 'order' (order_MAP = order_POLY + 1).  in addition, we have a 
+    // user-specified MAX order, which we should respect, regardless of the mode
+
+    // set the max order (0 = constant) which the number of psf stars can support:
+    // rule of thumb: require 3 stars per 'cell' (order+1)^2
+    int MaxOrderForStars = 0;
+    if (Npsf >= 12) MaxOrderForStars = 1; // 4 cells
+    if (Npsf >= 27) MaxOrderForStars = 2; // 9 cells
+    if (Npsf >= 48) MaxOrderForStars = 3; // 16 cells
+    if (Npsf >  75) MaxOrderForStars = 4; // 25 cells
+
+    pmTrend2DMode mode = PM_TREND_MAP;
+    if (mode == PM_TREND_MAP) {
+	MaxOrderForStars ++;
+    } 
+    APTREND_ORDER_MAX = PS_MIN (APTREND_ORDER_MAX, MaxOrderForStars);
+
+    psFree (psf->ApTrend);
+    psf->ApTrend = NULL;
+    float errorFloorMin = FLT_MAX;
+
+    // as we loop over orders, we need to refer to the initial selection, but we modify the
+    // option values to match the current guess: save the max values here:
+    int NX = readout->image->numCols;
+    int NY = readout->image->numRows;
+    for (int i = 1; i <= APTREND_ORDER_MAX; i++) {
+	
+	int Nx, Ny;
+	if (NX > NY) {
+	    Nx = i;
+	    Ny = PS_MAX (1, (int)(i * (NY / (float)(NX)) + 0.5));
+	} else {
+	    Ny = i;
+	    Nx = PS_MAX (1, (int)(i * (NX / (float)(NY)) + 0.5));
+	}
+
+	float errorFloor;
+        pmTrend2D *apTrend = psphotApResidTrend (&errorFloor, readout, Nx, Ny, xPos, yPos, apResid, dMag);
+	if (!apTrend) {
+	    continue;
+	}
+
+	// apply ApTrend results
+	// float xc = 0.5*readout->image->numCols + readout->image->col0 + 0.5;
+	// float yc = 0.5*readout->image->numRows + readout->image->row0 + 0.5;
+	// float ApResid = pmTrend2DEval (psf->ApTrend, xc, yc); // ap-fit at chip center
+	// if (!isfinite(ApResid)) psAbort("nan apresid @ center");
+
+        // store the minimum errorFloor and best ApTrend to keep
+        if (errorFloor < errorFloorMin) {
+            errorFloorMin = errorFloor;
+	    psFree (psf->ApTrend);
+	    psf->ApTrend = psMemIncrRefCounter(apTrend);
+        }
+	psFree (apTrend);
+    }
+    if (psf->ApTrend == NULL) {
+        psWarning("Failed to find a valid aperture residual value");
+	goto escape;
+    }
+
+    // apply ApTrend results
+    float xc = 0.5*readout->image->numCols + readout->image->col0 + 0.5;
+    float yc = 0.5*readout->image->numRows + readout->image->row0 + 0.5;
+
+    psf->ApResid  = pmTrend2DEval (psf->ApTrend, xc, yc); // ap-fit at chip center
+    psf->dApResid = errorFloorMin;
+    psf->nApResid = Npsf;
+
+    // save results for later output
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", psf->nApResid);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", psf->growth->apLoss);
+
+    psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "aperture residual: %f +/- %f\n", psf->ApResid, psf->dApResid);
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "measure full-frame aperture residuals for %d sources: %f sec\n", Npsf, psTimerMark ("psphot.apresid"));
+
+    psFree (xPos);
+    psFree (yPos);
+    psFree (apResid);
+    psFree (mag);
+    psFree (dMag);
+
+    psphotVisualPlotApResid (sources, psf->ApResid, psf->dApResid);
+
+    return true;
+
+escape:
+    // save nan values since these were not calculated
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   NAN);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", NAN);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "number of apresid stars", 0);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "aperture loss (mag)", NAN);
+
+    psFree (xPos);
+    psFree (yPos);
+    psFree (apResid);
+    psFree (mag);
+    psFree (dMag);
+    return true;
+    // this is a quality error, not a programming error
+}
+
+pmTrend2D *psphotApResidTrend (float *apResidSysErr, pmReadout *readout, int Nx, int Ny, psVector *xPos, psVector *yPos, psVector *apResid, psVector *dMag) {
+
+    // the mask marks the values not used to calculate the ApTrend
+    psVector *mask = psVectorAlloc(xPos->n, PS_TYPE_VECTOR_MASK);
+    psVectorInit (mask, 0);
+
+    // XXX allow user to set this optionally?
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    // measure Trend2D for the current spatial scale
+    pmTrend2D *apTrend = pmTrend2DAlloc (PM_TREND_MAP, readout->image, Nx, Ny, stats);
+
+    // XXX somewhat arbitrary: soften the errors so the few bright stars do not totally dominate:
+    // XXX use this or not?  probably not, since this is the point of the systematic error analysis
+    psVector *dMagSoft = psVectorAlloc (dMag->n, PS_TYPE_F32);
+    for (int i = 0; i < dMag->n; i++) {
+        dMagSoft->data.F32[i] = hypot(dMag->data.F32[i], 0.005);
+    }
+
+    // XXX test for errors here
+    if (!pmTrend2DFit (apTrend, mask, 0xff, xPos, yPos, apResid, dMagSoft)) {
+        psWarning("Failed to fit trend for %d x %d map", Nx, Ny);
+	psFree (apTrend);
+	return NULL;
+    }
+    if (apTrend->mode == PM_TREND_MAP) {
+	// p_psImagePrint (2, apTrend->map->map, "ApTrend Before"); // XXX TEST:
+	psImageMapRepair (apTrend->map->map);
+	// p_psImagePrint (2, apTrend->map->map, "ApTrend After"); // XXX TEST:
+    }
+
+    // construct the fitted values and the residuals
+    psVector *apResidFit = pmTrend2DEvalVector (apTrend, mask, 0xff, xPos, yPos);
+    psVector *apResidRes = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) apResidFit);
+
+    // measure systematic error
+    *apResidSysErr = psVectorSystematicError (apResidRes, dMag, 0.10);
+    if (!isfinite(*apResidSysErr)) {
+        psWarning("Failed to find systematic error for %d x %d map", Nx, Ny);
+	psFree (apTrend);
+	return NULL;
+    }
+
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "result of %d x %d grid\n", Nx, Ny);
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "systematic scatter floor: %f\n", *apResidSysErr);
+
+    if (psTraceGetLevel("psphot") >= 4) {
+	char filename[64];
+	snprintf (filename, 64, "apresid.%dx%d.dat", Nx, Ny);
+        FILE *dumpFile = fopen (filename, "w");
+        for (int i = 0; i < xPos->n; i++) {
+            fprintf (dumpFile, "%f %f  %f %f  %f %f  %x\n",
+                     xPos->data.F32[i], yPos->data.F32[i],
+                     dMag->data.F32[i], hypot(dMag->data.F32[i], *apResidSysErr),
+                     apResid->data.F32[i], apResidRes->data.F32[i],
+                     mask->data.PS_TYPE_VECTOR_MASK_DATA[i]);
+        }
+        fclose (dumpFile);
+    }
+
+    psFree (mask);
+    psFree (stats);
+    psFree (dMagSoft);
+    psFree (apResidFit);
+    psFree (apResidRes);
+
+    return apTrend;
+}
+
+bool psphotApResidMags_Threaded (psThreadJob *job) {
+
+    int Nskip = 0;
+    int Nfail = 0;
+
+    psScalar *scalar = NULL;
+
+    psArray *sources                = job->args->data[0];
+    pmPSF *psf                      = job->args->data[1];
+    pmSourcePhotometryMode photMode = PS_SCALAR_VALUE(job->args->data[2],S32);
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[3],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType markVal         = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+        if (source->type != PM_SOURCE_TYPE_STAR) SKIPSTAR ("NOT STAR");
+        if (source->mode &  PM_SOURCE_MODE_SATSTAR) SKIPSTAR ("SATSTAR");
+        if (source->mode &  PM_SOURCE_MODE_BLEND) SKIPSTAR ("BLEND");
+        if (source->mode &  PM_SOURCE_MODE_FAIL) SKIPSTAR ("FAIL STAR");
+        if (source->mode &  PM_SOURCE_MODE_POOR) SKIPSTAR ("POOR STAR");
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+
+	// clear the mask bit and set the circular mask pixels
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+	psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", markVal);
+
+	bool status = pmSourceMagnitudes (source, psf, photMode, maskVal);
+
+	// clear the mask bit 
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+	if (!status) {
+	    Nskip ++;
+	    psTrace ("psphot", 3, "skip : bad source mag");
+	    continue;
+	}
+    
+	if (!isfinite(source->apMag) || !isfinite(source->psfMag)) {
+	    Nfail ++;
+	    psTrace ("psphot", 3, "fail : nan mags : %f %f", source->apMag, source->psfMag);
+	    continue;
+	}
+	source->mode |= PM_SOURCE_MODE_AP_MAGS;
+    }
+
+    // change the value of a scalar on the array (wrap this and put it in psArray.h)
+    scalar = job->args->data[5];
+    scalar->data.S32 = Nskip;
+
+    scalar = job->args->data[6];
+    scalar->data.S32 = Nfail;
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotArguments.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotArguments.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotArguments.c	(revision 27622)
@@ -0,0 +1,251 @@
+# include "psphotStandAlone.h"
+
+static void writeHelpInfo(const char* program, pmConfig* config, FILE* ofile)
+{
+  fprintf(ofile,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "%s -help\n"
+	  "\n"
+	  "%s -version\n"
+	  "\n"
+	  "where:\n"
+	  "  FileNameList is a text file containing filenames, one per line\n"
+	  "  MaskFileNameList is a text file of mask filenames, one per line\n"
+	  "  VarFileNameList is a text file of variance filenames, one per line\n"
+	  "  OutFileBaseName is the 'root name' for output files\n"
+	  "\n"
+	  "additional options:\n"
+	  "  -modeltest xObj yObj [-model DEFAULT|ModelName] \n"
+	  "        [-fitmode DEFAULT|PSF|CONV] [-fitset FitFileName]\n"
+	  "     Test fit for object at the given coordinates.  ModelName\n"
+	  "     is one of PS_MODEL_GAUSS, PS_MODEL_PGAUSS, PS_MODEL_QGAUSS,\n"
+	  "     PS_MODEL_RGAUSS, PS_MODEL_PS1_V1, or PS_MODEL_SERSIC.\n"
+	  "     FitFileName is a file of x,y,Io triples.\n"
+	  "  -psf PsfFile1[,PsfFile2,...] or -psflist PsfFileNameList\n"
+	  "     specify PSF rather than letting %s estimate it\n"
+	  "  -src SrcFile1[,SrcFile2,...] or -srclist SrcFileNameList\n"
+	  "     specify additional sources for PSF generation\n"
+	  "  -chip nn[,nn,...]\n"
+	  "     select detector chips to process; default is all.\n"
+	  "     Indices correspond to zero-based offset in the FPA metadata table.\n"
+	  "  -photcode PhotoCodeName\n"
+	  "     specify photocode\n"
+	  "  -region RegionString\n"
+	  "     specify analysis region.  String is of form '[x0:x1,y0:y1]'\n"
+	  "     To use this option you must define a default in psphot.config\n"
+	  "  -visual\n"
+	  "     turns on interactive display mode\n"
+	  "  -dumpconfig CfgFileName\n"
+          "     causes config info to be dumped to the named file.\n"
+	  "  -break NOTHING|BACKMDL|PEAKS|MOMENTS|PSFMODEL|ENSEMBLE|PASS1\n"
+	  "     choose a point at which to exit processing early\n"
+	  "  -nthreads n\n"
+	  "     set number of parallel threads of execution\n"
+	  "  -F OldFileRule ReplacementFileRule\n"
+	  "     change file naming rule; e.g. '-F PSPHOT.OUTPUT PSPHOT.OUT.CMF.MEF'\n"
+	  "  -D name stringval\n"
+	  "     set a string-valued config parameter\n"
+	  "  -Di name intval\n"
+	  "     set an integer-valued config parameter\n"
+	  "  -Df name fval\n"
+	  "     set a float-valued config parameter\n"
+	  "  -Db name boolval\n"
+	  "     set a boolean-valued config parameter\n"
+	  "  -v, -vv, -vvv\n"
+	  "     set increasing levels of verbosity\n"
+	  "  -logfmt FormatString\n"
+	  "     set format string used for log messages\n"
+	  "  -trace Fac Lvl\n"
+	  "     set tracing for facility Fac to integer Lvl, e.g. '-trace err 10'\n"
+	  "  -trace-levels\n"
+	  "     print current trace levels\n",
+	  program,program,program,program,program);
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_SUCCESS);
+}
+
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  pmConfig *config,      // Configuration
+		  int exitCode
+		  ) 
+{
+  fprintf(stderr,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "Try '%s -help' for more options and explanation\n",
+	  program,program,program);
+    if (exitCode != PS_EXIT_SUCCESS)
+      psErrorStackPrint(stderr, "Error reading arguments\n");
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(exitCode);
+}
+
+pmConfig *psphotArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+      psErrorStackPrint(stderr, "Can't read site configuration");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    PSARGUMENTS_INSTANTIATE_GENERICS( psphot, config, argc, argv );
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // Number of threads is handled
+    PSARGUMENTS_INSTANTIATE_THREADSARG( psphot, config, argc, argv )
+
+    // run the test model (requires X,Y coordinate)
+    if ((N = psArgumentGet (argc, argv, "-modeltest"))) {
+        if (argc<=N+2) {
+          psErrorStackPrint(stderr, "Expected to see 2 more arguments; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+
+        psMetadataAddBool (options, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_Y", 0, "", atof(argv[N+2]));
+
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+
+        // specify the modeltest model
+        if ((N = psArgumentGet (argc, argv, "-model"))) {
+	    if (argc<=N+1) {
+	      psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	      exit(PS_EXIT_CONFIG_ERROR);
+	    }
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODEL", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+
+        // specify the test fit mode
+        if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+	    if (argc<=N+1) {
+	      psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	      exit(PS_EXIT_CONFIG_ERROR);
+	    }
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+        if ((N = psArgumentGet (argc, argv, "-fitset"))) {
+	    if (argc<=N+1) {
+	      psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	      exit(PS_EXIT_CONFIG_ERROR);
+	    }
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+    }
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // visual : interactive display mode
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    //
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",       "-mask",     "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "VARIANCE",   "-variance", "-variancelist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf",      "-psflist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC",        "-src",      "-srclist");
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (psArgumentGet(argc, argv, "-help") ||
+	psArgumentGet(argc, argv, "-h"))
+      writeHelpInfo(argv[0], config, stdout);
+      
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotBasicDeblend.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotBasicDeblend.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotBasicDeblend.c	(revision 27622)
@@ -0,0 +1,169 @@
+# include "psphotInternal.h"
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotBasicDeblend (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotBasicDeblendReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on basic deblend analysis for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotBasicDeblendReadout (pmConfig *config, const pmFPAview *view, const char *filename, int fileIndex) {
+
+    int N;
+    bool status;
+    float threshold;
+    pmSource *source, *testSource;
+
+    psTimerStart ("psphot.deblend.basic");
+
+    int Nblend = 0;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, fileIndex); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping basic deblend");
+	return true;
+    }
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    float FRACTION = psMetadataLookupF32 (&status, recipe, "DEBLEND_PEAK_FRACTION");
+    if (!status) FRACTION = 0.25;
+
+    float NSIGMA   = psMetadataLookupF32 (&status, recipe, "DEBLEND_SKY_NSIGMA");
+    if (!status) NSIGMA = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // source analysis is done in peak order (brightest first)
+    // we use an index for this so the spatial sorting is kept
+    psVector *SN = psVectorAlloc (sources->n, PS_DATA_F32);
+    for (int i = 0; i < SN->n; i++) {
+        source = sources->data[i];
+        SN->data.F32[i] = source->peak->SN;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+        N = index->data.U32[i];
+        source = sources->data[N];
+
+        if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+
+        // temporary array for overlapping objects we find
+        psArray *overlap = psArrayAllocEmpty (100);
+
+        // search backwards for overlapping sources
+        for (int j = N - 1; j >= 0; j--) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) break;
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        // search forwards for overlapping sources
+        for (int j = N + 1; j < sources->n; j++) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        if (overlap->n == 0) {
+            psFree (overlap);
+            continue;
+        }
+
+        // this source has overlapping neighbors, check for actual blends
+        // generate source contour (1/4 peak counts)
+        // set the threshold based on user inputs
+
+        // threshold is fraction of the source peak flux
+        // image is background subtracted; source->moments->Sky should always be 0.0
+        threshold = FRACTION * source->peak->SN;
+        // threshold is no less than NSIGMA
+        threshold = PS_MAX (threshold, NSIGMA);
+
+        // generate a basic contour (set of x,y coordinates at-or-below flux level)
+        psArray *contour = pmSourceContour (source->pixels, source->peak->x, source->peak->y, threshold);
+        if (contour == NULL) {
+            psFree (overlap);
+            continue;
+        }
+
+        // the source contour consists of two vectors, xv and yv.  the contour is
+        // a series of coordinate pairs, (xv[i],yv[i]) & (xv[i+1],yv[i+1]).  both
+        // coordinate pairs have the same yv[] value, with xv[i] corresponding to
+        // the left boundary and xv[i+1] corresponding to the right boundary
+
+	// a blend can only be associated with one primary source
+
+        psVector *xv = contour->data[0];
+        psVector *yv = contour->data[1];
+        for (int k = 0; k < overlap->n; k++) {
+            testSource = overlap->data[k];
+	    if (testSource->mode & PM_SOURCE_MODE_BLEND) continue;
+            if (testSource->peak->value > source->peak->value) continue;
+            for (int j = 0; j < xv->n; j+=2) {
+                if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+                if (xv->data.F32[j+0] > testSource->peak->x) break;
+                if (xv->data.F32[j+1] < testSource->peak->x) break;
+
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+
+                // add this to the list of source->blends
+                if (source->blends == NULL) {
+                    source->blends = psArrayAllocEmpty (16);
+                }
+                psArrayAdd (source->blends, 16, testSource);
+
+                Nblend ++;
+                j = xv->n;
+            }
+        }
+        psFree (overlap);
+        psFree (contour);
+    }
+    psLogMsg ("psphot.deblend", PS_LOG_INFO, "identified %d blended objects: %f sec\n", Nblend, psTimerMark ("psphot.deblend.basic"));
+
+    psFree (SN);
+    psFree (index);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotBlendFit.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotBlendFit.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotBlendFit.c	(revision 27622)
@@ -0,0 +1,316 @@
+# include "psphotInternal.h"
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotBlendFit (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotBlendFitReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to fit sources (non-linear) for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// XXX I don't like this name
+bool psphotBlendFitReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    int Nfit = 0;
+    int Npsf = 0;
+    int Next = 0;
+    int Nfail = 0;
+    bool status;
+
+    psTimerStart ("psphot.fit.nonlinear");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend fit");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // source fitting parameters for extended source fits
+    int fitIter = psMetadataLookupS32(&status, recipe, "EXT_FIT_ITER"); // Max number of fit iterations
+    assert (status && fitIter > 0);
+
+    float fitTol = psMetadataLookupF32 (&status, recipe, "EXT_FIT_TOL"); // Fit tolerance
+    assert (status && isfinite(fitTol) && fitTol > 0);
+
+    bool poisson = psMetadataLookupBool(&status, recipe, "POISSON.ERRORS.PHOT.LMM"); // Poisson errors?
+    assert (status);
+
+    float skySig = psMetadataLookupF32(&status, recipe, "SKY_SIG");
+    assert (status && isfinite(skySig) && skySig > 0);
+
+    // Define source fitting parameters for extended source fits
+    pmSourceFitModelInit(fitIter, fitTol, PS_SQR(skySig), poisson);
+
+    psphotInitLimitsPSF (recipe, readout);
+    psphotInitLimitsEXT (recipe);
+    psphotInitRadiusPSF (recipe, readout->analysis, psf->type);
+
+    // starts the timer, sets up the array of fitSets
+    psphotFitInit (nThreads);
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping blend");
+	return true;
+    }
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+	    // allocate a job -- if threads are not defined, this just runs the job
+	    psThreadJob *job = psThreadJobAlloc ("PSPHOT_BLEND_FIT");
+	    psArray *newSources = psArrayAllocEmpty(16);
+
+	    psArrayAdd(job->args, 1, readout);
+	    psArrayAdd(job->args, 1, recipe);
+	    psArrayAdd(job->args, 1, cells->data[j]); // sources
+	    psArrayAdd(job->args, 1, psf);
+	    psArrayAdd(job->args, 1, newSources); // return for new sources
+	    psFree (newSources);
+
+	    PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfit
+	    PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Npsf
+	    PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Next
+	    PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfail
+
+	    if (!psThreadJobAddPending(job)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+		psFree (job);
+		return NULL;
+	    }
+	    psFree(job);
+
+# if (0)
+            {
+                int nfit = 0;
+                int npsf = 0;
+                int next = 0;
+                int nfail = 0;
+                psArray *newSources = psArrayAllocEmpty(16);
+
+                if (!psphotBlendFit_Unthreaded (&nfit, &npsf, &next, &nfail, readout, recipe, cells->data[j], psf, newSources)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                    return NULL;
+                }
+                Nfit += nfit;
+                Npsf += npsf;
+                Next += next;
+                Nfail += nfail;
+
+                // add these back onto sources
+                for (int k = 0; k < newSources->n; k++) {
+                    psArrayAdd (sources, 16, newSources->data[k]);
+                }
+                psFree (newSources);
+            }
+# endif
+        }
+
+	// wait for the threads to finish and manage results
+	if (!psThreadPoolWait (false)) {
+	    psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+	    return NULL;
+	}
+
+	// we have only supplied one type of job, so we can assume the types here
+	psThreadJob *job = NULL;
+	while ((job = psThreadJobGetDone()) != NULL) {
+	    if (job->args->n < 1) {
+		fprintf (stderr, "error with job\n");
+	    } else {
+		psScalar *scalar = NULL;
+		scalar = job->args->data[5];
+		Nfit += scalar->data.S32;
+		scalar = job->args->data[6];
+		Npsf += scalar->data.S32;
+		scalar = job->args->data[7];
+		Next += scalar->data.S32;
+		scalar = job->args->data[8];
+		Nfail += scalar->data.S32;
+
+		// add these back onto sources
+		psArray *newSources = job->args->data[4];
+		for (int j = 0; j < newSources->n; j++) {
+		    psArrayAdd (sources, 16, newSources->data[j]);
+		}
+	    }
+	    psFree(job);
+            }
+    }
+    psFree (cellGroups);
+
+    if (psTraceGetLevel("psphot") >= 6) {
+      psphotSaveImage (NULL, readout->image,  "image.v2.fits");
+    }
+
+    psLogMsg ("psphot.psphotBlendFit", PS_LOG_INFO, "fit models: %f sec for %d objects (%d psf, %d ext, %d failed, %ld skipped)\n", psTimerMark ("psphot.fit.nonlinear"), Nfit, Npsf, Next, Nfail, sources->n - Nfit);
+
+    psphotVisualShowResidualImage (readout);
+    psphotVisualShowFlags (sources);
+
+    return true;
+}
+
+bool psphotBlendFit_Threaded (psThreadJob *job) {
+
+    bool status = false;
+    int Nfit = 0;
+    int Npsf = 0;
+    int Next = 0;
+    int Nfail = 0;
+    psScalar *scalar = NULL;
+
+    pmReadout *readout  = job->args->data[0];
+    psMetadata *recipe  = job->args->data[1];
+    psArray *sources    = job->args->data[2];
+    pmPSF *psf          = job->args->data[3];
+    psArray *newSources = job->args->data[4];
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // S/N limit to perform full non-linear fits
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, recipe, "FULL_FIT_SN_LIM");
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        if (source->mode &  PM_SOURCE_MODE_CR_LIMIT) continue;
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // skip DBL second sources (ie, added by psphotFitBlob
+        if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+
+        // limit selection to some SN limit
+        if (source->peak->SN < FIT_SN_LIM) continue;
+
+        // exclude sources outside optional analysis region
+        if (source->peak->xf < AnalysisRegion.x0) continue;
+        if (source->peak->yf < AnalysisRegion.y0) continue;
+        if (source->peak->xf > AnalysisRegion.x1) continue;
+        if (source->peak->yf > AnalysisRegion.y1) continue;
+
+        // if model is NULL, we don't have a starting guess
+        if (source->modelPSF == NULL) continue;
+
+        // skip sources which are insignificant flux?
+        // XXX this is somewhat ad-hoc
+        if (source->modelPSF->params->data.F32[1] < 0.1) {
+            psTrace ("psphot", 5, "skipping near-zero source: %f, %f : %f\n",
+                     source->modelPSF->params->data.F32[1],
+                     source->modelPSF->params->data.F32[2],
+                     source->modelPSF->params->data.F32[3]);
+            continue;
+        }
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+        Nfit ++;
+
+        // try fitting PSFs or extended sources depending on source->mode
+        // these functions subtract the resulting fitted source
+        if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+            if (psphotFitBlob (readout, source, newSources, psf, maskVal, markVal)) {
+                source->type = PM_SOURCE_TYPE_EXTENDED;
+                psTrace ("psphot", 5, "source at %7.1f, %7.1f is ext", source->peak->xf, source->peak->yf);
+                Next ++;
+		source->mode |= PM_SOURCE_MODE_NONLINEAR_FIT;
+                continue;
+            }
+        } else {
+            if (psphotFitBlend (readout, source, psf, maskVal, markVal)) {
+                source->type = PM_SOURCE_TYPE_STAR;
+                psTrace ("psphot", 5, "source at %7.1f, %7.1f is psf", source->peak->xf, source->peak->yf);
+                Npsf ++;
+		source->mode |= PM_SOURCE_MODE_NONLINEAR_FIT;
+                continue;
+            }
+        }
+
+        psTrace ("psphot", 5, "source at %7.1f, %7.1f failed", source->peak->xf, source->peak->yf);
+        Nfail ++;
+
+        // re-subtract the object, leave local sky
+        pmSourceCacheModel (source, maskVal);
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    // change the value of a scalar on the array (wrap this and put it in psArray.h)
+    scalar = job->args->data[5];
+    scalar->data.S32 = Nfit;
+
+    scalar = job->args->data[6];
+    scalar->data.S32 = Npsf;
+
+    scalar = job->args->data[7];
+    scalar->data.S32 = Next;
+
+    scalar = job->args->data[8];
+    scalar->data.S32 = Nfail;
+
+    return true;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotCheckStarDistribution.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotCheckStarDistribution.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotCheckStarDistribution.c	(revision 27622)
@@ -0,0 +1,105 @@
+# include "psphotInternal.h"
+
+// examine the x,y distribution of the psfstars and extend the selection if needed
+// desired region and coverage are specified by pmPSFOptions
+// even if stars->n is at the limit, we will add more sources to fill out the area
+
+bool psphotCheckStarDistribution (psArray *stars, psArray *sources, pmPSFOptions *options) {
+
+    // count the number of PSFSTAR sources in each cell.  for polynomial representations, we
+    // use an image (Norder + 1) x (Norder + 1)
+    int Nx = options->psfTrendNx;
+    int Ny = options->psfTrendNy;
+    if (options->psfTrendMode != PM_TREND_MAP) {
+	Nx ++;
+	Ny ++;
+    }
+
+    // set up and image and an image binning structure to cover the field
+    psImage *nCell = psImageAlloc (Nx, Ny, PS_TYPE_S32);
+    psImageInit (nCell, 0);
+
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXruff = Nx;
+    binning->nYruff = Ny;
+    binning->nXfine = options->psfFieldNx;
+    binning->nYfine = options->psfFieldNy;
+
+    psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkipByOffset (binning, options->psfFieldXo, options->psfFieldYo);
+
+    // where are the PSF stars located?
+    for (int i = 0; i < stars->n; i++) {
+	
+	pmSource *source = stars->data[i];
+        if (source->peak == NULL) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+	int binX = psImageBinningGetRuffX (binning, source->peak->xf);
+	int binY = psImageBinningGetRuffY (binning, source->peak->yf);
+
+	assert (binX >= 0);
+	assert (binY >= 0);
+	assert (binX <  nCell->numCols);
+	assert (binY <  nCell->numRows);
+
+	nCell->data.S32[binY][binX] ++;
+    }
+	
+    // do any cells have too few PSFSTAR sources?
+    // we use 3 as a minimum (slightly arbitrary...)
+    for (int iy = 0; iy < nCell->numRows; iy++) { 
+	for (int ix = 0; ix < nCell->numCols; ix++) { 
+	    if (nCell->data.S32[iy][ix] < 3) {
+		int nNew = psphotSupplementStars (stars, sources, binning, ix, iy);
+		if (nNew) {
+		    psLogMsg ("pmObjects", 3, "added %d fainter PSF candidates to %d,%d\n", nNew, ix, iy);
+		} else {
+		    psLogMsg ("pmObjects", 3, "tried to add to %d,%d, but no more sources are available\n", ix, iy);		    
+		}
+	    }
+	}
+    }
+
+    psFree (nCell);
+    psFree (binning);
+    return true;
+}
+
+// select more possible PSF stars in the specified cell
+// sources should be sorted by SN before calling this function
+int psphotSupplementStars (psArray *stars, psArray *sources, psImageBinning *binning, int ix, int iy) {
+
+    int nNew = 0;
+
+    float Xs = psImageBinningGetFineX (binning, ix);
+    float Xe = psImageBinningGetFineX (binning, ix + 1);
+
+    float Ys = psImageBinningGetFineY (binning, iy);
+    float Ye = psImageBinningGetFineY (binning, iy + 1);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	// add sources which are marked as stars, in S/N order until...
+	pmSource *source = sources->data[i];
+        if (source->peak == NULL) continue;
+        if (source->moments == NULL) continue;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) continue;
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+
+	float x = source->peak->xf;
+	float y = source->peak->yf;
+
+	if (x < Xs) continue;
+	if (y < Ys) continue;
+	if (x > Xe) continue;
+	if (y > Ye) continue;
+
+	source->mode |= PM_SOURCE_MODE_PSFSTAR;
+	psArrayAdd (stars, 200, source);
+
+	nNew ++;
+    }
+    return nNew;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotChoosePSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotChoosePSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotChoosePSF.c	(revision 27622)
@@ -0,0 +1,519 @@
+# include "psphotInternal.h"
+
+// generate a PSF model for inputs without PSF models already loaded
+bool psphotChoosePSF (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotChoosePSFReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to choose a psf model for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// try PSF models and select best option
+bool psphotChoosePSFReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status;
+
+    psTimerStart ("psphot.choose.psf");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // do not generate a PSF if we already were supplied one
+    if (psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF")) {
+        psLogMsg ("psphot", PS_LOG_DETAIL, "psf model supplied for input file %d", index);
+        return true;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping PSF model");
+        return false;
+    }
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    psAssert (maskVal, "missing mask value?");
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    psAssert (markVal, "missing mark value?");
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // structure to store user options defining the psf
+    pmPSFOptions *options = pmPSFOptionsAlloc ();
+
+    // load user options from the recipe. no need to check existence -- they are
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    assert (status);
+
+    // supply the measured sky variance for optional constant errors (non-poissonian)
+    float SKY_SIG = psMetadataLookupF32 (&status, recipe, "SKY_SIG");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsPhotLMM = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PHOT.LMM");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsPhotLin = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PHOT.LIN");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsParams = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PARAMS");
+    assert (status);
+
+    // how to model the PSF variations across the field
+    options->psfTrendMode = pmTrend2DModeFromString (psMetadataLookupStr (&status, recipe, "PSF.TREND.MODE"));
+    assert (status);
+
+    options->psfTrendNx = psMetadataLookupS32 (&status, recipe, "PSF.TREND.NX");
+    assert (status);
+
+    options->psfTrendNy = psMetadataLookupS32 (&status, recipe, "PSF.TREND.NY");
+    assert (status);
+
+    // get the fixed PSF fit radius
+    // XXX check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    // options->radius = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    // assert (status);
+
+    // values on readout->analysis if we have calculated a Gaussian window function, use that
+    // for both the PSF fit radius and the aperture radius (scaling SIGMA), otherwise base the value on the recipe value for MOMENTS_GAUSS_SIGMA
+    float gaussSigma = psMetadataLookupF32 (&status, readout->analysis, "MOMENTS_GAUSS_SIGMA");
+    if (!status) {
+        gaussSigma = psMetadataLookupF32 (&status, recipe, "MOMENTS_GAUSS_SIGMA");
+    }
+    float fitScale = psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS_SCALE");
+    float apScale = psMetadataLookupF32(&status, recipe, "PSF_APERTURE_SCALE");
+    options->fitRadius = (int)(fitScale*gaussSigma);
+    options->apRadius = (int)(apScale*gaussSigma);
+
+    // XXX use the same radii for standard analysis as for the PSF creation
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "PSF_FIT_RADIUS", PS_META_REPLACE, "fit radius", options->fitRadius);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "PSF_APERTURE", PS_META_REPLACE, "psf aperture", options->apRadius);
+
+    // dimensions of the field for which the PSF is defined
+    options->psfFieldNx = readout->image->numCols;
+    options->psfFieldNy = readout->image->numRows;
+    options->psfFieldXo = readout->image->col0;
+    options->psfFieldYo = readout->image->row0;
+
+    int fitIter = psMetadataLookupS32(&status, recipe, "PSF_FIT_ITER"); // Maximum number of fit iterations
+    if (!status || fitIter <= 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PSF_FIT_ITER is not positive");
+        return false;
+    }
+    float fitTol = psMetadataLookupF32 (&status, recipe, "PSF_FIT_TOL"); // Fit tolerance
+    if (!status || !isfinite(fitTol) || fitTol <= 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PSF_FIT_TOL is not positive");
+        return false;
+    }
+    pmSourceFitModelInit(fitIter, fitTol, PS_SQR(SKY_SIG), options->poissonErrorsPhotLMM);
+
+    psArray *stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+            // keep NSTARS PSF stars, unmark the rest
+            if (stars->n < NSTARS) {
+                psArrayAdd (stars, 200, source);
+            } else {
+                source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+            }
+        }
+    }
+
+    // check that the identified psf stars sufficiently cover the region; if not, extend the
+    // limits somewhat
+    psphotCheckStarDistribution (stars, sources, options);
+
+    psLogMsg ("psphot.pspsf", PS_LOG_DETAIL, "selected candidate %ld PSF objects\n", stars->n);
+
+    if (stars->n < 50) {
+        // ROBUST is too agressive if we only have a small number of PSF stars
+        options->stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    } else {
+        options->stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    }
+
+    // get the list pointers for the PSF_MODEL entries
+    psArray *modelNames = NULL;
+    psMetadataItem *item = psMetadataLookup (recipe, "PSF_MODEL");
+    if (item == NULL) psAbort("missing PSF_MODEL selection");
+
+    if (item->type == PS_DATA_STRING) {
+        modelNames = psArrayAlloc(1);
+        modelNames->data[0] = psStringCopy (item->data.V);
+    } else {
+        if (item->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+        modelNames = psListToArray (item->data.list);
+    }
+
+    // generate a psf model using the first selection
+    if (stars->n == 0) {
+        psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+        bool mdok;                      // Status of MD lookup
+        if (!psMetadataLookupBool(&mdok, recipe, PSPHOT_RECIPE_PSF_FAKE_ALLOW)) {
+            return false;
+        }
+        psErrorStackPrint(stderr, "Using guess PSF model");
+        psErrorClear();
+
+        psFree(options);
+
+        // unset the PSFSTAR flags (none are used):
+        for (int i = 0; i < sources->n; i++) {
+            pmSource *source = sources->data[i];
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+
+        // XXX set sxx, etc from FWHM in recipe
+        pmPSF *psf = pmPSFBuildSimple (modelNames->data[0], 1.0, 1.0, 0.0, 1.0);
+        psf->fieldNx = readout->image->numCols;
+        psf->fieldNy = readout->image->numRows;
+        psFree (modelNames);
+
+        bool status = true;
+        status &= psphotMakeFluxScale (readout->image, recipe, psf);
+        status &= psphotPSFstats (readout, psf);
+        if (!status) {
+            psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+            psFree (psf);
+            return NULL;
+        }
+
+        // XXX set DSX_MEAN, etc?
+        return psf;
+    }
+
+    // set up an array to store the results
+    psArray *models = psArrayAlloc (modelNames->n);
+
+    // try each model option listed in config
+    for (int i = 0; i < modelNames->n; i++) {
+        char *modelName = modelNames->data[i];
+        pmPSFtry *try = pmPSFtryModel (stars, modelName, options, maskVal, markVal); // Attempt at fit
+        if (!try) {
+            // No big deal --- we'll try another model
+            psErrorClear();
+            continue;
+        }
+        models->data[i] = try;
+    }
+
+    // select the best of the models
+    // here we are using the clippedStdev on the metric as the indicator
+    int bestN = -1;
+    float bestM = 0.0;
+    for (int i = 0; i < models->n; i++) {
+        pmPSFtry *try = models->data[i];
+        if (try == NULL) {
+            psTrace ("psphot", 3, "PSF model %d is NULL", i);
+            continue;
+        }
+        float M = try->psf->dApResid;
+        if (bestN < 0 || M < bestM) {
+            bestM = M;
+            bestN = i;
+        }
+    }
+
+    // use the best model:
+    if (bestN < 0) {
+        psFree (models);
+        psFree (stars);
+
+        psErrorStackPrint (stderr, "Failed to fit any models when choosing PSF");
+        psErrorClear();
+
+        bool mdok;                      // Status of MD lookup
+        if (!psMetadataLookupBool(&mdok, recipe, PSPHOT_RECIPE_PSF_FAKE_ALLOW)) {
+            psFree (modelNames);
+            psFree(options);
+            return NULL;
+        }
+
+        // generate a psf model using the first selection
+        psLogMsg ("psphot.pspsf", PS_LOG_INFO, "Using guess PSF model");
+
+        // unset the PSFSTAR flags (none are used):
+        for (int i = 0; i < sources->n; i++) {
+            pmSource *source = sources->data[i];
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+
+        // XXX set sxx, etc from FWHM in recipe
+        pmPSF *psf = pmPSFBuildSimple (modelNames->data[0], 1.0, 1.0, 0.0, 1.0);
+        psf->fieldNx = readout->image->numCols;
+        psf->fieldNy = readout->image->numRows;
+        psFree (modelNames);
+
+        bool status = true;
+        status &= psphotMakeFluxScale (readout->image, recipe, psf);
+        status &= psphotPSFstats (readout, psf);
+        if (!status) {
+            psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+            psFree (psf);
+            return NULL;
+        }
+
+        // XXX set DSX_MEAN, etc?
+        return psf;
+    }
+
+    psFree (modelNames);
+    psFree (stars);
+
+    pmPSFtry *try = models->data[bestN];
+
+    // unset the PSFSTAR flag for stars not used for PSF model
+    for (int i = 0; i < try->sources->n; i++) {
+        pmSource *source = try->sources->data[i];
+        if (try->mask->data.PS_TYPE_VECTOR_MASK_DATA[i]) {
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+    }
+
+    // build a PSF residual image
+    if (!psphotMakeResiduals (try->sources, recipe, try->psf, maskVal)) {
+        psError(PSPHOT_ERR_PSF, false, "Unable to construct residual table for PSF");
+        psFree (models);
+        psFree(options);
+        return NULL;
+    }
+
+    // build the flux-to-magnitude conversion information
+    if (!psphotMakeFluxScale (readout->image, recipe, try->psf)) {
+        psError(PSPHOT_ERR_PSF, false, "Unable to construct flux scale for PSF");
+        psFree (models);
+        psFree(options);
+        return NULL;
+    }
+
+    // build curve-of-growth vector for this psf
+    if (!psphotMakeGrowthCurve (readout, recipe, try->psf)) {
+        psError(PSPHOT_ERR_PSF, false, "Unable to construct flux scale for PSF");
+        psFree (models);
+        psFree(options);
+        return NULL;
+    }
+
+    // XXX test dump of psf star data and psf-subtracted image
+    if (psTraceGetLevel("psphot.psfstars") > 5) {
+        psphotDumpPSFStars (readout, try, options->fitRadius, maskVal, markVal);
+    }
+
+    // save only the best model;
+    pmPSF *psf = psMemIncrRefCounter(try->psf);
+    psFree (models);
+
+    if (!psphotPSFstats (readout, psf)) {
+        psError(PSPHOT_ERR_PSF, false, "cannot measure PSF shape terms");
+        psFree(options);
+        psFree(psf);
+        return NULL;
+    }
+
+    char *modelName = pmModelClassGetName (psf->type);
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "select psf model: %f sec\n", psTimerMark ("psphot.choose.psf"));
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
+
+    psFree (options);
+
+    // save PSF on readout->analysis
+    if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot psf model", psf)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "problem saving sources on readout");
+        return false;
+    }
+    psFree (psf); // XXX double-check
+
+    // move into psphotChoosePSF
+    psphotVisualShowPSFModel (readout, psf);
+
+    return true;
+}
+
+// measure average parameters of the PSF model
+bool psphotPSFstats (pmReadout *readout, pmPSF *psf) {
+
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+
+    psImage *image = readout->image;
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    // XXX a minor hack: measure the values for a grid of points across the image, determine mean, UQ, LQ, etc:
+    psVector *fwhmMajor = psVectorAllocEmpty (100, PS_DATA_F32);
+    psVector *fwhmMinor = psVectorAllocEmpty (100, PS_DATA_F32);
+
+    for (float ix = -0.4; ix <= +0.4; ix += 0.1) {
+        for (float iy = -0.4; iy <= +0.4; iy += 0.1) {
+
+            // use the center of the center pixel of the image
+            float xc = ix*image->numCols + 0.5*image->numCols + image->col0 + 0.5;
+            float yc = iy*image->numRows + 0.5*image->numRows + image->row0 + 0.5;
+
+            // create modelPSF from this model
+            pmModel *modelPSF = pmModelFromPSFforXY (psf, xc, yc, 1.0);
+            if (!modelPSF) {
+                fprintf (stderr, "?");
+                continue;
+            }
+
+            // get the model full-width at half-max
+            float FWHM_MAJOR = 2*modelPSF->modelRadius (modelPSF->params, 0.5);
+
+            // XXX make sure this is consistent with the re-definition of PM_PAR_SXX
+            shape.sx  = modelPSF->params->data.F32[PM_PAR_SXX];
+            shape.sy  = modelPSF->params->data.F32[PM_PAR_SYY];
+            shape.sxy = modelPSF->params->data.F32[PM_PAR_SXY];
+            axes = psEllipseShapeToAxes (shape, 20.0);
+            psFree (modelPSF);
+
+            float FWHM_MINOR = FWHM_MAJOR * (axes.minor / axes.major);
+            if (!isfinite(FWHM_MAJOR) || !isfinite(FWHM_MINOR)) {
+                fprintf (stderr, "!");
+                continue;
+            }
+            psVectorAppend (fwhmMajor, FWHM_MAJOR);
+            psVectorAppend (fwhmMinor, FWHM_MINOR);
+        }
+    }
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV | PS_STAT_SAMPLE_QUARTILE);
+    if (!psVectorStats (stats, fwhmMajor, NULL, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failure to measure stats for FWHM MAJOR");
+        return false;
+    }
+
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MAJ",   PS_META_REPLACE, "PSF FWHM Major axis (mean)", stats->sampleMean);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_SG",   PS_META_REPLACE, "PSF FWHM Major axis (sigma)", stats->sampleStdev);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_LQ",   PS_META_REPLACE, "PSF FWHM Major axis (lower quartile)", stats->sampleLQ);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_UQ",   PS_META_REPLACE, "PSF FWHM Major axis (upper quartile)", stats->sampleUQ);
+
+    float fwhmMaj = stats->sampleMean;  // FWHM on major axis
+
+    if (!psVectorStats (stats, fwhmMinor, NULL, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failure to measure stats for FWHM MINOR");
+        return false;
+    }
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MIN",   PS_META_REPLACE, "PSF FWHM Minor axis (mean)", stats->sampleMean);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_SG",   PS_META_REPLACE, "PSF FWHM Minor axis (sigma)", stats->sampleStdev);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_LQ",   PS_META_REPLACE, "PSF FWHM Minor axis (lower quartile)", stats->sampleLQ);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_UQ",   PS_META_REPLACE, "PSF FWHM Minor axis (upper quartile)", stats->sampleUQ);
+
+    float fwhmMin = stats->sampleMean;  // FWHM on minor axis
+    if (readout->parent) {
+        pmChip *chip = readout->parent->parent; // Parent chip
+        psAssert(chip, "Cell should be attached to a chip.");
+        psMetadataItem *item = psMetadataLookup(chip->concepts, "CHIP.SEEING"); // Item with chip
+        item->data.F32 = 0.5 * (fwhmMaj + fwhmMin);
+    }
+
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           axes.theta);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", psf->nPSFstars);
+    psMetadataAddBool(readout->analysis, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", true);
+
+    psFree (fwhmMajor);
+    psFree (fwhmMinor);
+    psFree (stats);
+    return true;
+}
+
+// determine approximate PSF shape parameters based on the moments
+bool psphotMomentsStats (pmReadout *readout, psArray *sources) {
+
+    // without the PSF model, we can only rely on the source->moments
+    // as a measure of the FWHM values
+
+    int FWHM_N = 0;
+    double FWHM_X = 0.0;
+    double FWHM_Y = 0.0;
+    double FWHM_T = 0.0;
+
+    psEllipseMoments moments;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!source) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+        if (!source->moments) {
+            continue;
+        }
+
+        moments.x2 = source->moments->Mxx;
+        moments.y2 = source->moments->Myy;
+        moments.xy = source->moments->Mxy;
+
+        // limit axis ratio < 20.0
+        axes = psEllipseMomentsToAxes (moments, 20.0);
+
+        FWHM_X += axes.major * 2.35;
+        FWHM_Y += axes.minor * 2.35;
+        FWHM_T += axes.theta;
+        FWHM_N ++;
+    }
+
+    FWHM_X /= FWHM_N;
+    FWHM_Y /= FWHM_N;
+    FWHM_T /= FWHM_N;
+
+    if (readout->parent) {
+        pmChip *chip = readout->parent->parent; // Parent chip
+        psAssert(chip, "Cell should be attached to a chip.");
+        psMetadataItem *item = psMetadataLookup(chip->concepts, "CHIP.SEEING"); // Item with chip
+        item->data.F32 = 0.5 * (FWHM_X + FWHM_Y);
+    }
+
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MAJ",   PS_META_REPLACE, "PSF FWHM Major axis (mean)", FWHM_X);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_SG",   PS_META_REPLACE, "PSF FWHM Major axis (sigma)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_LQ",   PS_META_REPLACE, "PSF FWHM Major axis (lower quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MJ_UQ",   PS_META_REPLACE, "PSF FWHM Major axis (upper quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FWHM_MIN",   PS_META_REPLACE, "PSF FWHM Minor axis (mean)", FWHM_Y);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_SG",   PS_META_REPLACE, "PSF FWHM Minor axis (sigma)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_LQ",   PS_META_REPLACE, "PSF FWHM Minor axis (lower quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "FW_MN_UQ",   PS_META_REPLACE, "PSF FWHM Minor axis (upper quartile)", 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           FWHM_T);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", 0);
+    psMetadataAddBool(readout->analysis, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", false);
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotCleanup.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotCleanup.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotCleanup.c	(revision 27622)
@@ -0,0 +1,47 @@
+# include "psphotStandAlone.h"
+
+void psphotCleanup (pmConfig *config) {
+
+    // Dump configuration if requested
+    bool status;
+    psString dump_file = psMetadataLookupStr(&status, config->arguments, "DUMP_CONFIG");
+    if (dump_file) {
+        (void)pmConfigCamerasCull(config,NULL);
+	(void)pmConfigRecipesCull(config,NULL);
+        pmConfigDump(config, dump_file);
+    }
+
+    psFree (config);
+
+    psThreadPoolFinalize ();
+    psTimerStop ();
+    psMemCheckCorruption (stderr, true);
+    pmModelClassCleanup ();
+    psTimeFinalize ();
+    pmSourceFitSetDone ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "psphot");
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psphot");
+    return;
+}
+
+psExit psphotGetExitStatus (void) {
+
+    psErrorCode err = psErrorCodeLast ();
+    switch (err) {
+      case PS_ERR_NONE:
+        return PS_EXIT_SUCCESS;
+      case PSPHOT_ERR_SYS:
+        return PS_EXIT_SYS_ERROR;
+      case PSPHOT_ERR_CONFIG:
+        return PS_EXIT_CONFIG_ERROR;
+      case PSPHOT_ERR_PROG:
+        return PS_EXIT_PROG_ERROR;
+      case PSPHOT_ERR_DATA:
+        return PS_EXIT_DATA_ERROR;
+      default:
+        return PS_EXIT_UNKNOWN_ERROR;
+    }
+    return PS_EXIT_UNKNOWN_ERROR;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotCullPeaks.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotCullPeaks.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotCullPeaks.c	(revision 27622)
@@ -0,0 +1,35 @@
+# include "psphotInternal.h"
+
+/*
+ * Cull a set of peaks contained in a psArray of pmFootprints
+ */
+psErrorCode
+psphotCullPeaks(const psImage *image,   // the image wherein lives the footprint
+                const psImage *weight,  // corresponding variance image
+                const psMetadata *recipe,
+                psArray *footprints) {  // array of pmFootprints
+    bool status = false;
+    float nsigma_delta = psMetadataLookupF32(&status, recipe, "FOOTPRINT_CULL_NSIGMA_DELTA");
+    if (!status) {
+        nsigma_delta = 0; // min.
+    }
+    float nsigma_min = psMetadataLookupF32(&status, recipe, "FOOTPRINT_CULL_NSIGMA_MIN");
+    if (!status) {
+        nsigma_min = 0;
+    }
+    float fPadding = psMetadataLookupF32(&status, recipe, "FOOTPRINT_CULL_NSIGMA_PAD");
+    if (!status) {
+        fPadding = 0;
+    }
+    const float skyStdev = psMetadataLookupF32(NULL, recipe, "SKY_STDEV");
+    const float min_threshold = nsigma_min*skyStdev;
+    
+    for (int i = 0; i < footprints->n; i++) {
+	pmFootprint *fp = footprints->data[i];
+	if (pmFootprintCullPeaks(image, weight, fp, nsigma_delta, fPadding, min_threshold) != PS_ERR_NONE) {
+	    return psError(PS_ERR_UNKNOWN, false, "Culling pmFootprint %d", fp->id);
+	}
+    }
+    
+    return PS_ERR_NONE;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDeblendSatstars.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDeblendSatstars.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDeblendSatstars.c	(revision 27622)
@@ -0,0 +1,210 @@
+# include "psphotInternal.h"
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotDeblendSatstars (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotDeblendSatstarsReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on saturated star deblend analysis for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotDeblendSatstarsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int fileIndex) {
+
+    int N;
+    pmSource *source;
+    bool status;
+
+    psTimerStart ("psphot.deblend.sat");
+
+    int Nblend = 0;
+    float SAT_MIN_RADIUS = 5.0;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, fileIndex); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping satstar blend");
+	return true;
+    }
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    pmCell *cell = readout->parent;
+    float SATURATION = 0.75*psMetadataLookupF32 (&status, cell->concepts, "CELL.SATURATION");
+    float SAT_TEST_LEVEL = 0.5*SATURATION;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // source analysis is done in peak order (brightest first)
+    // we use an index for this so the spatial sorting is kept
+    psVector *SN = psVectorAlloc (sources->n, PS_DATA_F32);
+    for (int i = 0; i < SN->n; i++) {
+        source = sources->data[i];
+        SN->data.F32[i] = source->peak->SN;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+        N = index->data.U32[i];
+        source = sources->data[N];
+
+        // XXX filter? if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+        if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+        if (source->peak->flux < SATURATION) continue;
+
+	// save these for reference below
+	int xPeak = source->peak->x;
+	int yPeak = source->peak->y;
+
+        // generate a basic contour (set of x,y coordinates at-or-below flux level)
+        psArray *contour = pmSourceContour (source->pixels, xPeak, yPeak, SAT_TEST_LEVEL);
+        if (contour == NULL) continue;
+
+	// contour consists of a set of X,Y coords giving the boundary
+	psVector *xVec = contour->data[0];
+	psVector *yVec = contour->data[1];
+	if (xVec->n < 5) {
+	    psFree(contour);
+	    continue;
+	}
+
+	// find the center of the contour (let's just use mid[x,y])
+	int xMin = xVec->data.F32[0];
+	int xMax = xVec->data.F32[0];
+	int yMin = yVec->data.F32[0];
+	int yMax = yVec->data.F32[0];
+	for (int j = 0; j < xVec->n; j++) {
+	    xMin = PS_MIN (xMin, xVec->data.F32[j]);
+	    xMax = PS_MAX (xMax, xVec->data.F32[j]);
+	    yMin = PS_MIN (yMin, yVec->data.F32[j]);
+	    yMax = PS_MAX (yMax, yVec->data.F32[j]);
+	}	
+	int xCenter = 0.5*(xMin + xMax);
+	int yCenter = 0.5*(yMin + yMax);
+	psFree (contour);
+
+	psAssert (xCenter >= source->pixels->col0, "invalid shift in object center");
+	psAssert (xCenter <  source->pixels->col0 + source->pixels->numCols, "invalid shift in object center");
+	psAssert (yCenter >= source->pixels->row0, "invalid shift in object center");
+	psAssert (yCenter <  source->pixels->row0 + source->pixels->numRows, "invalid shift in object center");
+
+	// reset the peak for this source to the value of the center pixel
+	source->peak->x = xCenter;
+	source->peak->y = yCenter;
+	source->peak->xf = xCenter;
+	source->peak->yf = yCenter;
+	
+	// temporary array for overlapping objects we find
+        psArray *overlap = psArrayAllocEmpty (100);
+
+        // search backwards for overlapping sources
+        for (int j = N - 1; j >= 0; j--) {
+            pmSource *testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) break;
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        // search forwards for overlapping sources
+        for (int j = N + 1; j < sources->n; j++) {
+            pmSource *testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        if (overlap->n == 0) {
+            psFree (overlap);
+            continue;
+        }
+
+	// now find the contour which is at 0.5*SAT_TEST_LEVEL (xPeak, yPeak is valid high pixel)
+        contour = pmSourceContour (source->pixels, xPeak, yPeak, 0.5*SAT_TEST_LEVEL);
+        if (contour == NULL) {
+	    psFree (overlap);
+	    continue; 
+	}
+	
+	// any peaks within this contour should be dropped (mark as blend, drop after loop is done)
+	// also drop any peaks which are too close to this peak
+        psVector *xv = contour->data[0];
+        psVector *yv = contour->data[1];
+        for (int k = 0; k < overlap->n; k++) {
+            pmSource *testSource = overlap->data[k];
+	    float radius = hypot((testSource->peak->x - xCenter), (testSource->peak->y - yCenter));
+	    if (radius < SAT_MIN_RADIUS) {
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+                Nblend ++;
+		continue;
+	    }
+            for (int j = 0; j < xv->n; j+=2) {
+                if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+                if (xv->data.F32[j+0] > testSource->peak->x) break;
+                if (xv->data.F32[j+1] < testSource->peak->x) break;
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+                Nblend ++;
+                j = xv->n; // skip rest of contour
+            }
+        }
+        psFree (overlap);
+        psFree (contour);
+    }
+
+    // drop the sources marked as BLEND
+    for (int i = 0; i < sources->n;) {
+	pmSource *source = sources->data[i];
+
+        if (!(source->mode & PM_SOURCE_MODE_BLEND)) {
+	    i++;
+	    continue;
+	}
+
+	// shuffle the remaining sources forward
+	for (int j = i; j < sources->n - 1; j++) {
+	    sources->data[j] = sources->data[j+1];
+	}
+	psFree (source);
+	sources->n --;
+    }
+
+    psFree (SN);
+    psFree (index);
+
+    psLogMsg ("psphot", PS_LOG_INFO, "found %d satstar blend peaks, leaving %ld sources: %f sec\n", Nblend, sources->n, psTimerMark ("psphot.deblend.sat"));
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDefineFiles.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDefineFiles.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDefineFiles.c	(revision 27622)
@@ -0,0 +1,160 @@
+# include "psphotInternal.h"
+
+// List of output files
+static const char *outputFiles[] = { "PSPHOT.OUTPUT", "PSPHOT.RESID", "PSPHOT.BACKMDL",
+                                     "PSPHOT.BACKMDL.STDEV", "PSPHOT.BACKGND", "PSPHOT.BACKSUB",
+                                     "PSPHOT.PSF.SAVE", "SOURCE.PLOT.MOMENTS", "SOURCE.PLOT.PSFMODEL",
+                                     "SOURCE.PLOT.APRESID", NULL };
+
+// XXX we need to be able to distinguish several cases:
+// 1) the particular output data was not requested
+// 2) the particular output data was requested but was not generated (skipped)
+// 3) the particular output data was requested but was not generated (for valid failure)
+// 4) the particular output data was requested but was not generated (surprising failure)
+
+// define the needed / desired I/O files
+bool psphotDefineFiles (pmConfig *config, pmFPAfile *input) {
+
+    bool status = false;
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    // the output sources are carried on the input->fpa structures
+    pmFPAfile *outsources = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.OUTPUT");
+    if (!outsources) {
+        psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.OUTPUT");
+        return false;
+    }
+    outsources->save = true;
+
+    // if we are choosing not to save the output detections, mark this file inactive so it will
+    // not be written, but will keep the detections in memory for other functions (eg, astrometry)
+    if (!psMetadataLookupBool (NULL, recipe, "SAVE.OUTPUT")) {
+        pmFPAfileActivate (config->files, false, "PSPHOT.OUTPUT");
+        outsources->save = false;
+    }
+
+    // optionally save the residual image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.RESID")) {
+        pmFPAfile *output = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.RESID");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.RESID");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background model (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL")) {
+        int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+        int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input, DX, DY, "PSPHOT.BACKMDL");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background model's standard deviation (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL.STDEV")) {
+        int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+        int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input, DX, DY, "PSPHOT.BACKMDL.STDEV");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL.STDEV");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the full background image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKGND")) {
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input,  1,  1, "PSPHOT.BACKGND");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKGND");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background-subtracted image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKSUB")) {
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input,  1,  1, "PSPHOT.BACKSUB");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PSF")) {
+        pmFPAfile *output = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.PSF.SAVE");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.PSF.SAVE");
+            return false;
+        }
+        output->save = true;
+    }
+
+    // optionally save output plots
+    // allow specific plots only
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PLOTS")) {
+        pmFPAfile *output = NULL;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.MOMENTS");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.MOMENTS");
+            return false;
+        }
+        output->save = true;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.PSFMODEL");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.PSFMODEL");
+            return false;
+        }
+        output->save = true;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.APRESID");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.APRESID");
+            return false;
+        }
+        output->save = true;
+    }
+
+    if (psMetadataLookupPtr(NULL, config->arguments, "SRC")) {
+        if (!pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT.CMF", "SRC")) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.INPUT.CMF");
+            return status;
+        }
+    }
+
+    if (psMetadataLookupPtr(NULL, config->arguments, "SRCTEXT")) {
+	// XXX cannot use pmFPAfileDefineFromArgs: this is explicitly a FITS-based I/O function
+	// supply the attach the 
+	if (!psphotLoadSRCTEXT(input->fpa, config)) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to load PSPHOT.INPUT.TEXT");
+            return status;
+        }
+    }
+
+    if (psMetadataLookupPtr(NULL, config->arguments, "PSPHOT.PSF")) {
+        pmFPAfileBindFromArgs(&status, input, config, "PSPHOT.PSF.LOAD", "PSPHOT.PSF");
+        if (!status) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.PSF.LOAD");
+            return status;
+        }
+    }
+
+    // XXX add in example PSF image thumbnails
+    // pmFPAfileConstruct (config->files, format, config->camera, "PSPHOT.PSF_SAMPLE");
+
+    return true;
+}
+
+void psphotFilesActivate(pmConfig *config, bool state)
+{
+    for (int i = 0; outputFiles[i]; i++) {
+        if (!pmFPAfileActivate(config->files, state, outputFiles[i])) {
+            psErrorClear();
+        }
+    }
+
+    return;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetect.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetect.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetect.c	(revision 27622)
@@ -0,0 +1,41 @@
+# include "psphotDetect.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: psphotDetect [-mask image(s)] [-masklist masklist] [-psf psfmodel] [-points pointlist] (output)\n");
+    exit (PS_EXIT_CONFIG_ERROR);
+}
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();			// register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotDetectArguments (argc, argv);
+    if (!config) {
+	psErrorStackPrint(stderr, "Error reading arguments\n");
+	usage ();
+    }
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotDetectParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotDetectImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphotDetect image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphotDetect run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetect.h
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetect.h	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetect.h	(revision 27622)
@@ -0,0 +1,23 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#ifndef PSPHOT_DETECT_H
+#define PSPHOT_DETECT_H
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+// Top level functions
+pmConfig   *psphotDetectArguments(int argc, char **argv);
+bool        psphotDetectParseCamera (pmConfig *config);
+bool        psphotDetectImageLoop (pmConfig *config);
+bool        psphotDetectReadout(pmConfig *config, const pmFPAview *view);
+
+bool        psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile);
+void        psphotCleanup (pmConfig *config);
+psExit      psphotGetExitStatus (void);
+
+#endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectArguments.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectArguments.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectArguments.c	(revision 27622)
@@ -0,0 +1,57 @@
+# include "psphotDetect.h"
+
+pmConfig *psphotDetectArguments(int argc, char **argv) {
+
+    int N;
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-version"))) {
+        psString version;
+        version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+        psError(PSPHOT_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // XXX add psphot thread arguments?
+
+    // all three of these input files are required: if not found, we will exit
+    if (!pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",   "-mask",    "-masklist")) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "mask image not supplied");
+        return NULL;
+    }
+    if (!pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf", "-psflist")) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "mask image not supplied");
+        return NULL;
+    }
+    if (!pmConfigFileSetsMD (config->arguments, &argc, argv, "POINTS", "-points",  "-pointslist")) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "mask image not supplied");
+        return NULL;
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+        return NULL;
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotDetectArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectImageLoop.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectImageLoop.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectImageLoop.c	(revision 27622)
@@ -0,0 +1,97 @@
+# include "psphotDetect.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotDetectImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *mask = psMetadataLookupPtr (&status, config->files, "PSPHOT.MASK");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "Can't find input mask data!");
+	return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // for psphot, we force data to be read at the chip level 
+    while ((chip = pmFPAviewNextChip (view, mask->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+	// load just the input image data (image, mask, weight)
+	pmFPAfileActivate (config->files, false, NULL);
+	pmFPAfileActivate (config->files, true, "PSPHOT.MASK");
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+	// mosaic the cells of a chip into a single contiguous (trimmed) chip
+        if (!psphotMosaicChip(config, view, "PSPHOT.INPUT", "PSPHOT.MASK")) ESCAPE ("Unable to mosaic chip.");
+
+	// try to load other supporting data (PSF, SRC, etc).
+	// do not re-load the mask three files
+	pmFPAfileActivate (config->files, true, NULL);
+	pmFPAfileActivate (config->files, false, "PSPHOT.MASK");
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+	// re-activate files so they will be closed and freed below
+	pmFPAfileActivate (config->files, true, NULL);
+
+	// there is now only a single chip (multiple readouts?). loop over it and process
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		psLogMsg ("psphot", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+		if (!readout->data_exists) { continue; }
+
+		// run the actual photometry analysis on this chip/cell/readout
+		if (!psphotDetectReadout (config, view)) {
+		    psError(psErrorCodeLast(), false, "failure in psphotDetectReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+	    }
+	}
+
+	// save output which is saved at the chip level
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed output for Chip in psphot.");
+    }
+    // save output which is saved at the fpa level
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed ouput for FPA in psphot.");
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
+// PSPHOT.MASK
+// PSPHOT.WEIGHT
+// 
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectParseCamera.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectParseCamera.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectParseCamera.c	(revision 27622)
@@ -0,0 +1,43 @@
+# include "psphotDetect.h"
+
+// define the needed / desired I/O files
+bool psphotDetectParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // if MASK or WEIGHT was supplied on command line, bind files to 'load'
+    // the mask and weight will be mosaicked with the image
+    pmFPAfile *mask = pmFPAfileDefineFromArgs (&status, config, "PSPHOT.MASK", "MASK");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (!psphotSetMaskBits (config)) {
+      // XXX shouldn't this come after the file has been read?
+        psError (PS_ERR_UNKNOWN, false, "failed to set mask bit values");
+        return NULL;
+    }
+    mask->dataLevel = PM_FPA_LEVEL_CHIP; // force load at the CHIP level
+
+    // the psphot analysis is performed on chips
+    pmFPAfile *input = pmFPAfileDefineChipMosaic(config, mask->fpa, "PSPHOT.INPUT");
+    if (!input) {
+        psError(PSPHOT_ERR_CONFIG, false, _("Unable to generate new file from PSPHOT.INPUT"));
+        return NULL;
+    }
+
+    // the PSF model is required
+    pmFPAfileBindFromArgs(&status, mask, config, "PSPHOT.PSF.LOAD", "PSPHOT.PSF");
+    if (!status) {
+      psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.PSF.LOAD");
+      return status;
+    }
+
+    if (!pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT.PTS", "POINTS")) {
+      psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.INPUT.PTS");
+      return status;
+    }
+
+    psErrorClear();                     // some metadata lookup may have failed
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectReadout.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectReadout.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDetectReadout.c	(revision 27622)
@@ -0,0 +1,54 @@
+# include "psphotInternal.h"
+
+bool psphotDetectReadout(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
+    // for psphot envisions threading within psphotReadout, not multiple threads calling
+    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
+    // jointly by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, false);
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndWeight (config, readout, recipe);
+
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved in the recipe
+    pmPSF *psf = psphotLoadPSF (config, view, recipe);
+    psAssert (psf, "psf should be loaded");
+
+    // grab the sources of interest from the storage location (pmFPAfile PSPHOT.INPUT.CMF)
+    psArray *sources = psphotLoadPSFSources (config, view);
+    if (!sources) {
+      psError(PS_ERR_UNKNOWN, false, "No sources supplied to measure PSF");
+      return false;
+    }
+
+    // include externally-supplied sources (supplied as PSPHOT.INPUT.CMF)
+    psphotSetSourceParams (config, sources, psf);
+
+    // calculate source magnitudes
+    psphotPSFWeights(config, readout, view, sources);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+}
+
+// deep in pmSourcePixelWeight, we need the following items for each location of interest:
+// pmModel *model (should be a realized version of the PSF, can have unity normalization)
+// psImage *mask  (local subimage of mask for source)
+// psImageMaskType maskVal (from recipe & mask header)
+
+{ 
+    pmModel *model = pmModelFromPSFforXY (psf, x, y, 1.0);
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotDiagnosticPlots.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotDiagnosticPlots.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotDiagnosticPlots.c	(revision 27622)
@@ -0,0 +1,203 @@
+# include "psphotInternal.h"
+
+# if (HAVE_KAPA)
+
+static int kapa_fd = -1;
+
+int psphotKapaOpen ()
+{
+    char kapa[64];
+
+    strcpy (kapa, "kapa");
+
+    if (kapa_fd == -1) {
+        kapa_fd = KapaOpenNamedSocket (kapa, "psphot");
+    }
+    return kapa_fd;
+}
+
+bool psphotKapaClose ()
+{
+
+    if (kapa_fd == -1)
+        return true;
+    KapaClose (kapa_fd);
+    kapa_fd = -1;
+    return true;
+}
+
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy)
+{
+
+    KapaSection section;
+    Graphdata graphdata;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    psStats *stats = psStatsAlloc (PS_STAT_MAX | PS_STAT_MIN);
+    if (!psVectorStats (stats, values, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats for histogram");
+        return false;
+    }
+
+    psHistogram *histogram = psHistogramAlloc (stats->min, stats->max, 1000);
+    psVectorHistogram (histogram, values, NULL, NULL, 0);
+
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (kapa);
+
+    // plot 1 is the full range
+    section.x  = 0.0;
+    section.y  = 0.0;
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.name = strcreate ("bottom");
+    KapaSetSection (kapa, &section);
+    free (section.name);
+
+    // set limits based on data values
+    graphdata.xmin = stats->min;
+    graphdata.xmax = stats->max;
+
+    psStatsInit (stats);
+    stats->options = PS_STAT_MAX | PS_STAT_MIN;
+    if (!psVectorStats (stats, histogram->nums, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats for histogram");
+	psFree(histogram);
+        return false;
+    }
+
+    // scale the plot to hold the histogram
+    graphdata.ymin = stats->min - 0.05*(stats->max - stats->min);
+    graphdata.ymax = stats->max + 0.05*(stats->max - stats->min);
+
+    KapaSetLimits (kapa, &graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 1;
+    KapaPrepPlot (kapa, histogram->nums->n, &graphdata);
+    KapaPlotVector (kapa, histogram->nums->n, histogram->bounds->data.F32, "x");
+    KapaPlotVector (kapa, histogram->nums->n, histogram->nums->data.F32, "y");
+    psFree (histogram);
+
+    // plot 2 is the +/- 10 sigma
+    section.x  = 0.0;
+    section.y  = 0.5;
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.name = strcreate ("top");
+    KapaSetSection (kapa, &section);
+    free (section.name);
+
+    // +/- 10 sigma
+    graphdata.xmin = mean - 10.0*sigma;
+    graphdata.xmax = mean + 10.0*sigma;
+
+    histogram = psHistogramAlloc (graphdata.xmin, graphdata.xmax, 100);
+    psVectorHistogram (histogram, values, NULL, NULL, 0);
+
+    psStatsInit (stats);
+    stats->options = PS_STAT_MAX | PS_STAT_MIN;
+    if (!psVectorStats (stats, histogram->nums, NULL, NULL, 0)) {
+	psError(PS_ERR_UNKNOWN, false, "failure to measure stats for histogram");
+	psFree(histogram);
+        return false;
+    }
+
+    // scale the plot to hold the histogram
+    graphdata.ymin = stats->min - 0.05*(stats->max - stats->min);
+    graphdata.ymax = stats->max + 0.05*(stats->max - stats->min);
+
+    KapaSetLimits (kapa, &graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 1;
+    KapaPrepPlot (kapa, histogram->nums->n, &graphdata);
+    KapaPlotVector (kapa, histogram->nums->n, histogram->bounds->data.F32, "x");
+    KapaPlotVector (kapa, histogram->nums->n, histogram->nums->data.F32, "y");
+
+    char line[128];
+    sprintf (line, "sky: %f +/- %f, cell %d,%d", mean, sigma, ix, iy);
+    KapaSendLabel (kapa, line, KAPA_LABEL_XP);
+
+    // pause until user types 'return'
+    fprintf(stdout, "press return");
+    if (!fgets(line, 64, stdin)) {
+        // This is just to avoid a compiler warning on some systems; it's not really necessary
+        psWarning("Couldn't read anything.");
+    }
+
+    psFree (stats);
+    psFree (histogram);
+
+    return true;
+}
+
+bool psphotDiagnosticPlots (const pmConfig *config, const char *name, ...) {
+
+    va_list argPtr;
+    bool status;
+
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+    assert (plots);
+
+    // do we want the requested plot?
+    if (!psMetadataLookupBool (&status, plots, name)) return false;
+
+    // Get the variable list parameters to pass to allocation function
+    va_start(argPtr, name);
+
+    if (!strcmp(name, "IMAGE.BACKGROUND.CELL.HISTOGRAM")) {
+
+        int ix = va_arg(argPtr, psS32);
+        int iy = va_arg(argPtr, psS32);
+        float mean  = va_arg(argPtr, double);
+        float sigma = va_arg(argPtr, double);
+        psVector *values = va_arg(argPtr, psPtr);
+
+        int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+        assert (status);
+        int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+        assert (status);
+
+        bool gotX = (xPlot < 0) || (xPlot == ix);
+        bool gotY = (yPlot < 0) || (yPlot == iy);
+
+        if (gotX && gotY) {
+            psphotImageBackgroundCellHistogram (values, mean, sigma, ix, iy);
+            goto done;
+        }
+    }
+
+    // Clean up stack after variable arguement has been used
+done:
+    va_end(argPtr);
+    return true;
+}
+
+# else
+
+int psphotKapaOpen () { return -1; }
+bool psphotKapaClose () { return true; }
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy)
+{
+    return true;
+}
+bool psphotDiagnosticPlots (pmConfig *config, char *name, ...) { return true; }
+
+# endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotEfficiency.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotEfficiency.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotEfficiency.c	(revision 27622)
@@ -0,0 +1,507 @@
+# include "psphotInternal.h"
+
+#define MODEL_MASK (PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE | \
+                    PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_LIMITS) // Mask to apply to models
+
+# define TESTING 0
+
+// Calculate the limiting magnitude for an image
+//
+// We limit ourselves to calculating the peak flux in the smoothed image, which should be close (modulo
+// non-Gaussian PSF) to the limiting flux in the un-smoothed original image.
+static bool effLimit(float *magLim,           // Limiting magntiude, to return
+                     int *radius,             // Radius for fake sources, to return
+                     float *minFlux,          // Minimum flux for fake sources, to return
+                     float *norm,             // Normalisation of PSF (conversion: peak --> integrated flux)
+                     float *covarFactor,// Covariance factor
+                     const pmReadout *ro,     // Readout of interest
+                     pmPSF *psf,              // Point-spread function
+                     float thresh,            // Threshold for source identification
+                     float smoothSigma,       // Gaussian smoothing sigma
+                     float smoothNsigma,      // Smoothing limit
+                     psImageMaskType maskVal  // Value to mask
+                     )
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_VARIANCE(ro, false);
+    assert(magLim);
+    assert(radius);
+    assert(minFlux);
+    assert(norm);
+    assert(covarFactor);
+
+    psKernel *kernel = psImageSmoothKernel(smoothSigma, smoothNsigma); // Kernel used for smoothing
+    *covarFactor = psImageCovarianceCalculateFactor(kernel, ro->covariance);
+    psFree(kernel);
+
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for variance
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS);        // Random number generator
+    if (!psImageBackground(stats, NULL, ro->variance, ro->mask, maskVal, rng) ||
+        !isfinite(stats->robustMedian)) {
+        psError(PSPHOT_ERR_DATA, false, "Unable to determine mean variance");
+        psFree(stats);
+        psFree(rng);
+        return false;
+    }
+    psFree(rng);
+    float meanVar = stats->robustMedian; // Mean variance
+    psFree(stats);
+
+    // Need to normalise out difference between Gaussian and real PSF
+    pmModel *normModel = pmModelFromPSFforXY(psf, 0.5 * ro->variance->numCols,
+                                             0.5 * ro->variance->numRows, 1.0); // Model for normalisation
+    if (!normModel || (normModel->flags & MODEL_MASK)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate PSF model.");
+        psFree(normModel);
+        return false;
+    }
+    *norm = normModel->modelFlux(normModel->params); // Total flux for peak of 1.0
+    psFree(normModel);
+
+    // The signal-to-noise of the smoothed image is: S/N ~ I_smooth / sqrt(variance * factor)
+    // since the variance factor tells us the variance in the smoothed image.  Now, the trick is working
+    // out what the intensity in the smoothed image is, and how it is related to the flux.  We are
+    // convolving Io G_* with G_PSF/2pi.w^2, where G_* is the approximately-Gaussian PSF of the star,
+    // G_PSF is the pretty-close-matching Gaussian of the convolution kernel, Io is the peak flux in the
+    // unsmoothed image, and w is the width of the Gaussian.  Now, a normalised 2D Gaussian convolved
+    // with itself has a normalisation of 1/2pi(w^2+w^2) = 1/4pi.w^2.  Therefore:
+    // I_smooth = Flux / 2*norm.
+    float peakLim = thresh * sqrtf(meanVar * *covarFactor); // Limiting peak value in smoothed image
+    float fluxLim = 2.0 * *norm * peakLim; // Limiting flux in original
+    *magLim = -2.5 * log10f(fluxLim);
+    psTrace("psphot.fake", 1, "Limiting peak: %f\n", peakLim);
+    psTrace("psphot.fake", 1, "Limiting flux: %f\n", fluxLim);
+    psTrace("psphot.fake", 1, "Limiting mag: %f\n", *magLim);
+
+    *radius = smoothSigma * smoothNsigma;
+
+    *minFlux = 0.1 * sqrtf(meanVar);
+
+    return true;
+}
+
+/// Generate a fake image and add it in to the existing readout
+static bool effGenerate(psImage **xSrc, psImage **ySrc, // Positions of sources
+                        const pmReadout *ro,            // Readout of interest
+                        const pmPSF *psf,               // Point-spread function
+                        const psVector *magOffsets,     // Magnitude offsets for fake sources
+                        int numSources,                 // Number of fake sources for each bin
+                        float refMag,                   // Reference magnitude
+                        int radius,                     // Radius for fake sources
+                        float minFlux                   // Minimum flux for fake sources
+                        )
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_IMAGE(ro, false);
+    PS_ASSERT_VECTOR_NON_NULL(magOffsets, false);
+    PS_ASSERT_VECTOR_TYPE(magOffsets, PS_TYPE_F32, false);
+    assert(xSrc);
+    assert(ySrc);
+
+    int numBins = magOffsets->n;                                    // Number of bins
+    int numCols = ro->image->numCols, numRows = ro->image->numRows; // Size of image
+
+    *xSrc = psImageRecycle(*xSrc, numSources, numBins, PS_TYPE_F32);
+    *ySrc = psImageRecycle(*ySrc, numSources, numBins, PS_TYPE_F32);
+
+    psVector *xAll = psVectorAlloc(numBins * numSources, PS_TYPE_F32);
+    psVector *yAll = psVectorAlloc(numBins * numSources, PS_TYPE_F32);
+    psVector *magAll = psVectorAlloc(numBins * numSources, PS_TYPE_F32);
+
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
+    for (int i = 0, index = 0; i < numBins; i++) {
+        float mag = refMag + magOffsets->data.F32[i]; // Instrumental magnitude of sources
+
+        for (int j = 0; j < numSources; j++, index++) {
+            xAll->data.F32[index] = psRandomUniform(rng) * numCols;
+            yAll->data.F32[index] = psRandomUniform(rng) * numRows;
+            magAll->data.F32[index] = mag;
+        }
+        memcpy((*xSrc)->data.F32[i], &xAll->data.F32[i * numSources],
+               numSources * PSELEMTYPE_SIZEOF(PS_TYPE_F32));
+        memcpy((*ySrc)->data.F32[i], &yAll->data.F32[i * numSources],
+               numSources * PSELEMTYPE_SIZEOF(PS_TYPE_F32));
+    }
+    psFree(rng);
+
+    bool oldThreads = pmReadoutFakeThreads(true); // Old threading status
+
+    pmReadout *fakeRO = pmReadoutAlloc(NULL); // Fake readout
+    if (!pmReadoutFakeFromVectors(fakeRO, numCols, numRows, xAll, yAll, magAll,
+                                  NULL, NULL, psf, minFlux, radius, false, true)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate fake image");
+        psFree(fakeRO);
+        psFree(magAll);
+        psFree(xAll);
+        psFree(yAll);
+        return false;
+    }
+    psFree(magAll);
+    psFree(xAll);
+    psFree(yAll);
+
+    pmReadoutFakeThreads(oldThreads);
+
+    psBinaryOp(ro->image, ro->image, "+", fakeRO->image);
+    psFree(fakeRO);
+
+    return true;
+}
+
+bool psphotEfficiency (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotEfficiencyReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure detection efficiency for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// Determine detection efficiency
+bool psphotEfficiencyReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
+{
+    bool status = true;
+
+    psTimerStart("psphot.fake");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *realSources = detections->allSources;
+    psAssert (realSources, "missing sources?");
+
+    // XXX do we need to skip this step if we do not have a psf?
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
+
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+    PM_ASSERT_READOUT_IMAGE(readout, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+    PS_ASSERT_METADATA_NON_NULL(recipe, false);
+
+    // Collect recipe information
+    float smoothNsigma = psMetadataLookupF32(&status, recipe, "PEAKS_SMOOTH_NSIGMA"); // Smoothing limit
+    psAssert (status && isfinite(smoothNsigma), "Unable to find PEAKS_SMOOTH_NSIGMA in recipe (or invalid value)");
+
+    float thresh = psMetadataLookupF32(&status, recipe, "PEAKS_NSIGMA_LIMIT_2");
+    psAssert (status && isfinite(thresh), "Unable to find PEAKS_NSIGMA_LIMIT_2 in recipe (or invalid value)");
+
+    psVector *magOffsets = psMetadataLookupVector(&status, recipe, "EFF.MAG"); // Magnitude offsets
+    psAssert (status, "Unable to find EFF.MAG F32 vector in recipe");
+    psAssert (magOffsets->type.type == PS_TYPE_F32, "Unable to find EFF.MAG F32 vector in recipe");
+
+    int numSources = psMetadataLookupS32(&status, recipe, "EFF.NUM"); // Number of sources for each bin
+    psAssert (status && (numSources > 0), "Unable to find EFF.NUM in recipe (or invalid value)");
+
+    float minGauss = psMetadataLookupF32(&status, recipe, "PEAKS_MIN_GAUSS"); // Minimum valid fraction of kernel
+    psAssert (status && isfinite(minGauss), "PEAKS_MIN_GAUSS is not set in recipe (or invalid)");
+
+    // find the PSF size information (why is this not part of the psf structure?)
+    float fwhmMajor = psMetadataLookupF32(NULL, readout->analysis, "FWHM_MAJ"); // PSF size in x
+    float fwhmMinor = psMetadataLookupF32(NULL, readout->analysis, "FWHM_MIN"); // PSF size in y
+    if (!isfinite(fwhmMajor) || !isfinite(fwhmMinor) || fwhmMajor == 0.0 || fwhmMinor == 0.0) {
+        psError(PSPHOT_ERR_CONFIG, false, "Unable to find FWHM_MAJ and FWHM_MIN in readout->analysis");
+        return false;
+    }
+
+    float smoothSigma = 0.5*(fwhmMajor + fwhmMinor) / (2.0*sqrtf(2.0*log(2.0))); // Gaussian smoothing sigma
+    int numCols = readout->image->numCols, numRows = readout->image->numRows; // Size of image
+    int numBins = magOffsets->n;                          // Number of bins
+
+    psImageMaskType maskVal = psMetadataLookupImageMask(NULL, recipe, "MASK.PSPHOT"); // Value to mask
+
+    // remove all sources, adding noise for subtracted sources
+    psphotRemoveAllSources(realSources, recipe);
+
+#if TESTING
+    {
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS);
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                readout->image->data.F32[y][x] = psRandomGaussian(rng);
+                readout->variance->data.F32[y][x] = 1.0;
+                readout->mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] = 0;
+            }
+        }
+        psFree(readout->covariance);
+        readout->covariance = NULL;
+        psFree(rng);
+    }
+#endif
+
+
+
+    float magLim;                       // Guess at limiting magnitude
+    int radius;                         // Radius for fake sources
+    float minFlux;                      // Minimum flux for fake sources
+    float norm;                         // Normalisation of PSF
+    float covarFactor;                  // Covariance factor
+    if (!effLimit(&magLim, &radius, &minFlux, &norm, &covarFactor, readout,
+                  psf, thresh, smoothSigma, smoothNsigma, maskVal)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine limits for image");
+        return false;
+    }
+
+#if TESTING
+    psphotSaveImage(NULL, readout->image, "orig_image.fits");
+    psphotSaveImage(NULL, readout->variance, "orig_variance.fits");
+    psphotSaveImage(NULL, readout->mask, "orig_mask.fits");
+#endif
+
+    psImage *xFake = NULL, *yFake = NULL; // Coordinates of sources, each bin in a row
+    if (!effGenerate(&xFake, &yFake, readout, psf, magOffsets,
+                     numSources, magLim, radius, minFlux)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate fake sources");
+        psFree(xFake);
+        psFree(yFake);
+        return false;
+    }
+
+#if TESTING
+    psphotSaveImage(NULL, readout->image, "fake_image.fits");
+    psphotSaveImage(NULL, readout->variance, "fake_variance.fits");
+    psphotSaveImage(NULL, readout->mask, "fake_mask.fits");
+#endif
+
+    // XXX Could speed this up significantly by only convolving the central pixels of each fake source
+    psVector *significance = NULL;       // Significance image
+    {
+        int num = numSources * numBins; // Total number of sources
+        // Pixel coordinates of sources
+        psVector *xPix = psVectorAlloc(num, PS_TYPE_S32);
+        psVector *yPix = psVectorAlloc(num, PS_TYPE_S32);
+        for (int i = 0, index = 0; i < numBins; i++) {
+            for (int j = 0; j < numSources; j++, index++) {
+                float x = xFake->data.F32[i][j];
+                float y = yFake->data.F32[i][j];
+                xPix->data.S32[index] = PS_MIN(x + 0.5, numCols - 1);
+                yPix->data.S32[index] = PS_MIN(y + 0.5, numRows - 1);
+            }
+        }
+
+        psVector *convImage = psImageSmoothMaskPixels(readout->image, readout->mask, maskVal, xPix, yPix,
+                                                      smoothSigma, smoothNsigma, minGauss); // Convolved image
+        if (!convImage) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to smooth image pixels");
+            psFree(xPix);
+            psFree(yPix);
+            psFree(xFake);
+            psFree(yFake);
+            return false;
+        }
+        psVector *convVar = psImageSmoothMaskPixels(readout->variance, readout->mask, maskVal, xPix, yPix,
+                                                    smoothSigma, smoothNsigma, minGauss); // Convolved varianc
+        if (!convVar) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to smooth variance pixels");
+            psFree(xPix);
+            psFree(yPix);
+            psFree(xFake);
+            psFree(yFake);
+            return false;
+        }
+
+        float factor = 1.0 / covarFactor; // Correction for covariance
+
+        const psImage *mask = readout->mask;  // Mask for readout
+        for (int i = 0; i < num; i++) {
+            float imageVal = convImage->data.F32[i]; // Image value
+            float varVal = convVar->data.F32[i]; // Variance value
+            int x = xPix->data.S32[i], y = yPix->data.S32[i]; // Coordinates
+            if (imageVal < 0 || varVal <= 0 || (mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal)) {
+                convImage->data.F32[i] = 0.0;
+            } else {
+                convImage->data.F32[i] = factor * PS_SQR(imageVal) / varVal;
+            }
+        }
+
+        psFree(xPix);
+        psFree(yPix);
+
+        significance = convImage;
+        psFree(convVar);
+    }
+
+    thresh *= thresh;                   // "Significance" is actually significance-squared
+
+    psVector *count = psVectorAlloc(numBins, PS_TYPE_S32); // Number of sources found in each bin
+    psArray *fakeSources = psArrayAlloc(numSources);            // Fake sources in each bin
+    psArray *fakeSourcesAll = psArrayAllocEmpty(numSources * numBins); // All fake sources
+    for (int i = 0, index = 0; i < numBins; i++) {
+        int numFound = 0;               // Number found
+
+        // Determine extraction size
+        float mag = magLim + magOffsets->data.F32[i]; // Magnitude for bin
+        float peak = powf(10.0, -0.4 * mag) / norm;   // Peak flux
+        pmModel *model = pmModelFromPSFforXY(psf, numCols / 2.0, numRows / 2.0, peak); // Model for source
+        if (!model || (model->flags & MODEL_MASK)) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to generate model for bin %d", i);
+            psFree(model);
+            return false;
+        }
+        float sourceRadius = PS_MAX(radius, model->modelRadius(model->params, minFlux)); // Radius for source
+        psFree(model);
+
+        psArray *sources = psArrayAllocEmpty(numSources); // Sources in this bin
+        for (int j = 0; j < numSources; j++, index++) {
+            // Coordinates of interest
+            float sig = significance->data.F32[index]; // Significance of pixel
+            if (sig > thresh) {
+                pmSource *source = pmSourceAlloc(); // Fake source
+                float x = xFake->data.F32[i][j];
+                float y = yFake->data.F32[i][j];
+                source->peak = pmPeakAlloc(x, y, sig, PM_PEAK_LONE);
+                if (!pmSourceDefinePixels(source, readout, x, y, sourceRadius)) {
+                    psErrorClear();
+                    continue;
+                }
+                source->peak->xf = x;
+                source->peak->yf = y;
+                source->modelPSF = pmModelFromPSFforXY(psf, x, y, 2.0 * sqrtf(sig));
+                source->type = PM_SOURCE_TYPE_STAR;
+
+                numFound++;
+                psArrayAdd(sources, sources->n, source);
+                psArrayAdd(fakeSourcesAll, fakeSourcesAll->n, source);
+                psFree(source);
+            }
+        }
+        fakeSources->data[i] = sources;
+        count->data.S32[i] = numFound;
+    }
+    psFree(xFake);
+    psFree(yFake);
+    psFree(significance);
+
+    if (!psphotFitSourcesLinearReadout(recipe, readout, fakeSourcesAll, psf, true)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to perform linear fit on fake sources.");
+        psFree(fakeSources);
+        psFree(count);
+        return false;
+    }
+
+    // Disable aperture corrections (save current value)
+    pmTrend2D *apTrend = psf->ApTrend;  // Aperture trend
+    psf->ApTrend = NULL;
+
+    if (!psphotMagnitudesReadout(config, recipe, view, readout, fakeSourcesAll, psf)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to measure magnitudes of fake sources.");
+        psFree(fakeSources);
+        psFree(count);
+        psf->ApTrend = apTrend; // Casting away const!
+        return false;
+    }
+    psFree(fakeSourcesAll);
+
+    // Replace aperture corrections
+    psf->ApTrend = apTrend;
+
+    psVector *magDiffMean = psVectorAlloc(numBins, PS_TYPE_F32); // Mean difference in magnitude for each bin
+    psVector *magDiffStdev = psVectorAlloc(numBins, PS_TYPE_F32); // Stdev of diff in magnitude for each bin
+    psVector *magErrMean = psVectorAlloc(numBins, PS_TYPE_F32); // Mean error in magnitude for each bin
+    psVector *magDiff = psVectorAlloc(numSources, PS_TYPE_F32);   // Magnitude differences
+    psVector *magErr = psVectorAlloc(numSources, PS_TYPE_F32);   // Magnitude errors
+    psVector *magMask = psVectorAlloc(numSources, PS_TYPE_VECTOR_MASK); // Mask for magnitude errors
+    psStats *magStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV |
+                                     PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV); // Statistics
+    for (int i = 0; i < numBins; i++) {
+        psStatsInit(magStats);
+        psVectorInit(magMask, 0);
+
+#if TESTING
+        psString name = NULL;
+        psStringAppend(&name, "fake_%d.dat", i);
+        FILE *file = fopen(name, "w");
+        psFree(name);
+#endif
+
+        float magRef = magLim + magOffsets->data.F32[i]; // Reference magnitude
+        psArray *sources = fakeSources->data[i];         // Sources in bin
+        for (int j = 0; j < sources->n; j++) {
+            pmSource *source = sources->data[j]; // Source of interest
+            if (!source || !isfinite(source->psfMag)) {
+                magMask->data.PS_TYPE_VECTOR_MASK_DATA[j] = 0xFF;
+                continue;
+            }
+
+#if TESTING
+            fprintf(file, "%f %f %f %f %f %f %f\n", source->peak->xf, source->peak->yf,
+                    source->modelPSF->params->data.F32[PM_PAR_XPOS],
+                    source->modelPSF->params->data.F32[PM_PAR_YPOS],
+                    magRef, source->psfMag, source->errMag);
+#endif
+            magDiff->data.F32[j] = source->psfMag - magRef;
+            magErr->data.F32[j] = source->errMag;
+        }
+        magDiff->n = sources->n;
+        magMask->n = sources->n;
+        magErr->n = sources->n;
+        if (!psVectorStats(magStats, magDiff, NULL, magMask, 0xFF)) {
+            // Probably because we don't have enough sources
+            psErrorClear();
+        }
+
+        if (isfinite(magStats->robustMedian) && isfinite(magStats->robustStdev)) {
+            magDiffMean->data.F32[i] = magStats->robustMedian;
+            magDiffStdev->data.F32[i] = magStats->robustStdev;
+        } else {
+            magDiffMean->data.F32[i] = magStats->sampleMean;
+            magDiffStdev->data.F32[i] = magStats->sampleStdev;
+        }
+
+        if (!psVectorStats(magStats, magErr, NULL, magMask, 0xFF)) {
+            // Probably because we don't have enough sources
+            psErrorClear();
+        }
+
+        if (isfinite(magStats->robustMedian)) {
+            magErrMean->data.F32[i] = magStats->robustMedian;
+        } else {
+            magErrMean->data.F32[i] = magStats->sampleMean;
+        }
+
+#if TESTING
+        fclose(file);
+#endif
+    }
+
+    psFree(magStats);
+    psFree(magDiff);
+    psFree(magMask);
+    psFree(magErr);
+
+    psFree(fakeSources);
+
+    pmDetEff *de = pmDetEffAlloc(magLim, numSources, numBins); // Detection efficiency
+    de->magOffsets = psVectorCopy(NULL, magOffsets, PS_TYPE_F32);
+    de->counts = count;
+    de->magDiffMean = magDiffMean;
+    de->magDiffStdev = magDiffStdev;
+    de->magErrMean = magErrMean;
+
+    psMetadataAddPtr(readout->analysis, PS_LIST_TAIL, PM_DETEFF_ANALYSIS, PS_META_REPLACE | PS_DATA_UNKNOWN,
+                     "Detection efficiency", de);
+    psFree(de);
+
+    psLogMsg("psphot", PS_LOG_INFO, "Detection efficiency: %lf sec\n", psTimerClear("psphot.fake"));
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotEllipticalContour.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotEllipticalContour.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotEllipticalContour.c	(revision 27622)
@@ -0,0 +1,174 @@
+# include "psphotInternal.h"
+
+// model parameters
+enum {PAR_PHI, PAR_EPSILON, PAR_RMIN};
+psF32 psphotEllipticalContourFunc (psVector *deriv, const psVector *params, const psVector *coord);
+
+bool psphotEllipticalContour (pmSource *source) {
+
+    pmSourceRadialProfile *profile = source->extpars->profile;
+
+    // use LMM to fit theta vs radius to an ellipse
+    psVector *theta = profile->theta;
+    psVector *radius = profile->isophotalRadii;
+
+    // find Rmin and Rmax for the initial guess
+    float Rmin = radius->data.F32[0];
+    float Rmax = radius->data.F32[0];
+
+    // arrays to hold the data to be fitted
+    // we fit x and y vs theta in separate passes.
+    psArray *x = psArrayAllocEmpty(2*radius->n);
+    psVector *y = psVectorAllocEmpty(2*radius->n, PS_TYPE_F32);
+    psVector *yErr = psVectorAllocEmpty(2*radius->n, PS_TYPE_F32);
+
+    int n = 0;
+    for (int i = 0; i < radius->n; i++) {
+	if (!isfinite(radius->data.F32[i])) continue;
+
+	psVector *coord = NULL;
+
+	// Rx coordinate value
+	coord = psVectorAlloc (2, PS_TYPE_F32);
+	coord->data.F32[1] = 0.0;
+	coord->data.F32[0] = theta->data.F32[i];
+	x->data[n] = coord;
+	y->data.F32[n] = radius->data.F32[i]*cos(theta->data.F32[i]);
+	yErr->data.F32[n] = 1000.0;
+	n++;
+
+	// Ry coordinate value
+	coord = psVectorAlloc (2, PS_TYPE_F32);
+	coord->data.F32[1] = 1.0;
+	coord->data.F32[0] = theta->data.F32[i];
+	x->data[n] = coord;
+	y->data.F32[n] = radius->data.F32[i]*sin(theta->data.F32[i]);
+	yErr->data.F32[n] = 1000.0;
+	n++;
+
+	// check the radius range
+	Rmin = MIN (Rmin, radius->data.F32[i]);
+	Rmax = MAX (Rmax, radius->data.F32[i]);
+    }	
+    x->n = n;
+    y->n = n;
+    yErr->n = n;
+
+    if (n < 4) {
+	psFree (x);
+	psFree (y);
+	psFree (yErr);
+	return false;
+    }
+
+    psVector *params = psVectorAlloc (3, PS_TYPE_F32);
+    
+    // psTraceSetLevel ("psLib.math.psMinimizeLMChi2", 7);
+    
+    // create the minimization constraints
+    psMinConstraint *constraint = psMinConstraintAlloc();
+
+    // XXX for now, no parameter masks, skip checkLimits
+    // XXX might help to add a limit to the angle or re-parameterize the ellipse in terms of Rxx, Ryy, Rxy
+    // constraint->checkLimits = psastroModelBoresiteLimits;
+
+    params->data.F32[PAR_PHI]     = 0.0;
+    params->data.F32[PAR_EPSILON] = Rmin / Rmax;
+    params->data.F32[PAR_RMIN]    = Rmin;
+
+    psMinimization *myMin = psMinimizationAlloc (25, 0.001);
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F32);
+    
+    // XXX skip the weights for now
+    psMinimizeLMChi2(myMin, covar, params, constraint, x, y, yErr, psphotEllipticalContourFunc);
+
+    /// XXX rationalize? if epsilon > 1, flip major and minor axes (rotate by 90 degrees)
+    if (params->data.F32[PAR_EPSILON] < 1.0) {
+	profile->axes.major = params->data.F32[PAR_RMIN] / params->data.F32[PAR_EPSILON];
+	profile->axes.minor = params->data.F32[PAR_RMIN];
+	profile->axes.theta = params->data.F32[PAR_PHI];
+    } else {
+	profile->axes.major = params->data.F32[PAR_RMIN];
+	profile->axes.minor = params->data.F32[PAR_RMIN] / params->data.F32[PAR_EPSILON];
+	profile->axes.theta = params->data.F32[PAR_PHI] + 0.5*M_PI;
+    }
+
+    psTrace ("psphot", 4, "# fitted values:\n");
+    psTrace ("psphot", 4, "Phi:   %f\n", profile->axes.theta*PS_DEG_RAD);
+    psTrace ("psphot", 4, "Rmaj:  %f\n", profile->axes.major);
+    psTrace ("psphot", 4, "Rmin:  %f\n", profile->axes.minor);
+    
+    // show the results
+    // psphotPetrosianVisualEllipticalContour (petrosian);
+
+    psFree (x);
+    psFree (y);
+    psFree (yErr);
+    psFree (params);
+    psFree (covar);
+    psFree (myMin);
+    psFree (constraint);
+
+    return true;
+}
+
+/**
+ * the full chisq is built of two associated sums over coordinates:
+ * chisq = sum ((Rx_obs - Rx_fit(t))^2 + (Ry_obs - Ry_fit(t))^2)
+ * we use split this into a 2x long vector and use coord[1] to distinguish the X and Y terms:
+ * coord[0] = measured X or measured Y
+ * coord[1] =          0 or          1
+ */
+psF32 psphotEllipticalContourFunc (psVector *deriv, const psVector *params, const psVector *coord) {
+
+    static int pass = 0;
+
+    psF32 *par = params->data.F32;
+
+    float alpha = coord->data.F32[0];
+
+    float cs_alpha = cos(alpha);
+    float sn_alpha = sin(alpha);
+
+    float cs_phi = cos(alpha - par[PAR_PHI]);
+    float sn_phi = sin(alpha - par[PAR_PHI]);
+
+    float r     = 1.0 / sqrt(SQ(sn_phi) + SQ(par[PAR_EPSILON]*cs_phi));
+    float r3    = pow(r, 3.0);
+    float drdE  = -0.5 * r3 * SQ(cs_phi) * 2.0 * par[PAR_EPSILON];
+    float drdP  = -0.5 * r3 * (SQ(par[PAR_EPSILON]) - 1) * 2.0 * cs_phi * sn_phi;
+
+    // value is X
+    // if (coord->data.F32[1] == 0) {
+    if (pass == 0) {
+	pass = 1;
+
+	float value = par[PAR_RMIN]*cs_alpha*r;
+
+	if (deriv) {
+	    psF32 *dPAR = deriv->data.F32;
+	    dPAR[PAR_RMIN]    = r*cs_alpha;
+	    dPAR[PAR_EPSILON] = par[PAR_RMIN]*cs_alpha*drdE;
+	    dPAR[PAR_PHI]     = 4.0*par[PAR_RMIN]*cs_alpha*drdP;
+	}
+	return (value);
+    }  
+
+    // value is Y
+    // if (coord->data.F32[1] == 1) {
+    if (pass == 1) {
+	pass = 0;
+
+	float value = par[PAR_RMIN]*sn_alpha*r;
+
+	if (deriv) {
+	    psF32 *dPAR = deriv->data.F32;
+	    dPAR[PAR_RMIN]    = r*sn_alpha;
+	    dPAR[PAR_EPSILON] = par[PAR_RMIN]*sn_alpha*drdE;
+	    dPAR[PAR_PHI]     = 4.0*par[PAR_RMIN]*sn_alpha*drdP;
+	}
+	return (value);
+    }  
+
+    psAbort ("programming error: invalid coordinate");
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotEllipticalProfile.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotEllipticalProfile.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotEllipticalProfile.c	(revision 27622)
@@ -0,0 +1,75 @@
+# include "psphotInternal.h"
+
+bool psphotEllipticalProfile (pmSource *source) {
+
+    pmSourceRadialProfile *profile = source->extpars->profile;
+
+    profile->radiusElliptical = psVectorAllocEmpty(100, PS_TYPE_F32);
+    profile->fluxElliptical = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    psVector *radius = profile->radiusElliptical;
+    psVector *flux = profile->fluxElliptical;
+
+    // the psEllipse functions use z = 0.5(x/Sxx)^2 + 0.5(y/Syy)^2 + x y Sxy
+    // which are converted to z = 0.5(x/a)^2 + 0.5(y/b)^2
+    // we have major and minor axes of a specific ellipse with r^2 = (x/A)^2 + (y/B)^2
+    // a = A / sqrt(2)
+
+    // we have the shape parameters of the elliptical contour at the reference isophote.
+    // use the axis ratio (major/minor) to rescale the radial profile so that 1 pixel
+    // along the major axis is 1 pixel, and a smaller amount on the minor axis
+
+    psEllipseAxes axes;
+    axes.major = M_SQRT1_2;
+    axes.minor = M_SQRT1_2 * (profile->axes.minor / profile->axes.major);
+
+    // axes.major = 1.0;
+    // axes.minor = profile->axes.minor / profile->axes.major;
+
+    axes.theta = profile->axes.theta;
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    float Sxx = shape.sx;
+    float Sxy = shape.sxy;
+    float Syy = shape.sy;
+
+    // XXX drop these two vectors?
+    psVector *radiusRaw = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *fluxRaw = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	for (int ix = 0; ix < source->pixels->numCols; ix++) {
+
+	    // 0.5 PIX: get radius as a function of pixel coord
+	    float x = ix + 0.5 - source->peak->xf + source->pixels->col0;
+	    float y = iy + 0.5 - source->peak->yf + source->pixels->row0;
+
+	    float r2 = 0.5*PS_SQR(x/Sxx) + 0.5*PS_SQR(y/Syy) + x*y*Sxy;
+
+	    psVectorAppend(radius, sqrt(r2));
+	    psVectorAppend(flux, source->pixels->data.F32[iy][ix]);
+
+	    float Rraw = hypot(x, y);
+	    psVectorAppend(radiusRaw, Rraw);
+	    psVectorAppend(fluxRaw, source->pixels->data.F32[iy][ix]);
+	}
+    }
+
+    // psVector *radiusRaw = psVectorAllocEmpty(100, PS_TYPE_F32);
+    // psVector *fluxRaw = psVectorAllocEmpty(100, PS_TYPE_F32);
+    // for (int i = 0; i < profile->radii->n; i++) {
+    //   psVector *r = profile->radii->data[i];
+    //   psVector *f = profile->fluxes->data[i];
+    //   for (int j = 0; j < r->n; j++) {
+    // 	psVectorAppend(radiusRaw, r->data.F32[j]);
+    // 	psVectorAppend(fluxRaw, f->data.F32[j]);
+    //   }
+    // }
+
+    // psphotPetrosianVisualProfileRadii (radius, flux, radiusRaw, fluxRaw, 0.0);
+    // psphotPetrosianVisualProfileByAngle (radius, flux);
+
+    psFree (radiusRaw);
+    psFree (fluxRaw);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.c.in
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.c.in	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.c.in	(revision 27622)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PSPHOT_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "psphotErrorCodes.h"
+
+void psphotErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSPHOT_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSPHOT_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSPHOT_ERR_NERROR - PSPHOT_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.dat
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.dat	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.dat	(revision 27622)
@@ -0,0 +1,18 @@
+#
+# This file is used to generate psphotErrorClasses.h
+#
+BASE = 3000		First value we use
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+FITS			Problem in FITS I/O
+FITS_WCS		Error interpreting FITS WCS information
+PHOTOM			Problem in photometry
+PSF			Problem in PSF
+APERTURE		Problem with aperture photometry
+SKY			Problem in sky determination
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.h.in
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.h.in	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotErrorCodes.h.in	(revision 27622)
@@ -0,0 +1,18 @@
+#if !defined(PSPHOT_ERROR_CODES_H)
+#define PSPHOT_ERROR_CODES_H
+/*
+ * The line
+ *  PSPHOT_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSPHOT_ERR_BASE = 3000,
+    PSPHOT_ERR_${ErrorCode},
+    PSPHOT_ERR_NERROR
+} psphotErrorCode;
+
+void psphotErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotEvalFLT.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotEvalFLT.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotEvalFLT.c	(revision 27622)
@@ -0,0 +1,64 @@
+# include "psphotInternal.h"
+
+bool psphotEvalEXT (pmSource *source, pmModel *model)
+{ 
+    int keep;
+
+    // do we actually have a valid EXT model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	psTrace ("psphot", 5, "no model fitted?\n");
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	psTrace ("psphot", 5, "no model fitted?\n");
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	psLogMsg ("psphot", 5, "EXT fail fit\n");
+	psTrace ("psphot", 5, "EXT fail fit\n");
+
+	if (model->flags & PM_MODEL_STATUS_OFFIMAGE) {
+	  psTrace ("psphot", 5, "off image\n");
+	}
+	if (model->flags & PM_MODEL_STATUS_BADARGS) {
+	  psTrace ("psphot", 5, "bad args\n");
+	}
+	if (model->flags & PM_MODEL_STATUS_NONCONVERGE) {
+	  psTrace ("psphot", 5, "non converge\n");
+	}
+	return false;
+    }
+
+    // unless we prove otherwise, this object is extended
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+
+    // the following source->mode information pertains to modelEXT:
+    source->mode |= PM_SOURCE_MODE_EXTMODEL;
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    // XXX this limit is fairly arbitrary, and must be > the value is the model limits
+    if (model->params->data.F32[PM_PAR_I0] <= 0.02) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	psTrace ("psphot", 5, "model central intensity ~ zero\n");
+	return false;
+    } 
+
+    keep = model->modelFitStatus(model);
+    if (keep) return true;
+
+    // poor-quality fit; only keep if nothing else works...
+    psLogMsg ("psphot", 5, "EXT poor fit\n");
+    psTrace ("psphot", 5, "EXT poor fit\n");
+
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotEvalPSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotEvalPSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotEvalPSF.c	(revision 27622)
@@ -0,0 +1,184 @@
+# include "psphotInternal.h"
+
+// given a pmSource which has been fitted using modelPSF, evaluate the resulting fit: did
+// the fit succeed? is this object saturated? return status is TRUE for a successful PSF
+// fit, FALSE otherwise.  
+
+// NOTE : 2008.10.12 EAM : This function used to make a measurement of the consistency of
+// the source shape with the PSF.  This feature has been superceded with the
+// much-better-defined psphotSourceSize.c function
+
+static float SATURATION;
+static float PSF_MIN_SN;
+static float PSF_MAX_CHI;
+
+bool psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout) {
+
+    bool status;
+
+    // XXX do we need to set this differently from the value used to mark saturated pixels?
+    pmCell *cell     = readout->parent;
+    SATURATION       = psMetadataLookupF32 (&status, cell->concepts, "CELL.SATURATION");
+    PSF_MIN_SN       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_SN");
+    PSF_MAX_CHI      = psMetadataLookupF32 (&status, recipe, "PSF_MAX_CHI");
+
+    return true;
+}
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalPSF (pmSource *source, pmModel *model) { 
+
+    int keep;
+    float SN, Chi;
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar was not fitted");
+	}
+	return false;
+    }
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar failed fit");
+	}
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0.02, the source is not viable
+    // perhaps the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0.02) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar failed fit (peak below 0)");
+	}
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+
+    SN  = model->params->data.F32[PM_PAR_I0]/model->dparams->data.F32[PM_PAR_I0];
+    Chi = model->chisqNorm / model->nDOF;
+
+    // assign PM_SOURCE_MODE_GOODSTAR to bright objects within PSF region of dparams[]
+    keep = TRUE;
+    keep &= (SN > PSF_MIN_SN);
+    keep &= (Chi < PSF_MAX_CHI);
+
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psTrace ("psphot", 5, "satstar fit results: %f, %f  %d :  SN: %f  Chisq: %f\n", 
+		     model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], keep, SN, Chi);
+    }
+
+    if (keep) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psTrace ("psphot", 7, "PSFSTAR kept (%f, %f  :  SN: %f Chisq: %f)\n", 
+		     model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], SN, Chi);
+	}
+	return true;
+    }
+
+    // this source is not a star, warn if it was a PSFSTAR
+    if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	psTrace ("psphot", 5, "PSFSTAR demoted based on fit quality   (%f, %f  :  SN: %f  Chisq: %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], SN, Chi);
+    } else {
+	psTrace ("psphot", 5, "fails PSF fit (%f, %f  :  SN: %f  Chisq: %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], SN, Chi);
+    }
+
+    // poor-quality fit; only keep if nothing else works...
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalDBL (pmSource *source, pmModel *model) { 
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	return false;
+    }
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0.02) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+    return true;
+}	
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotExtendedSourceAnalysis.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotExtendedSourceAnalysis.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotExtendedSourceAnalysis.c	(revision 27622)
@@ -0,0 +1,188 @@
+# include "psphotInternal.h"
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotExtendedSourceAnalysis (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    // perform full non-linear fits / extended source analysis?
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANALYSIS")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+	return true;
+    }
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotExtendedSourceAnalysisReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on measure extended source aperture-like parameters for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// aperture-like measurements for extended sources
+bool psphotExtendedSourceAnalysisReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status;
+    int Next = 0;
+    int Npetro = 0;
+    int Nisophot = 0;
+    int Nannuli = 0;
+    int Nkron = 0;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+	return true;
+    }
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // XXX require petrosian analysis for non-linear fits? 
+
+    // XXX temporary user-supplied systematic sky noise measurement (derive from background model)
+    float skynoise = psMetadataLookupF32 (&status, recipe, "SKY.NOISE");
+
+# if (0)
+    // if backModel or backStdev are missing, the values of sky and/or skyErr will be set to NAN
+    // XXX use this to set skynoise
+    pmReadout *backModel = psphotSelectBackground (config, view);
+    pmReadout *backStdev = psphotSelectBackgroundStdev (config, view);
+# endif
+
+    // S/N limit to perform full non-linear fits
+    float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
+
+    // which extended source analyses should we perform?
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // choose the sources of interest
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+
+	// skip PSF-like and non-astronomical objects
+	if (source->type == PM_SOURCE_TYPE_STAR) continue;
+	if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+	if (source->mode & PM_SOURCE_MODE_DEFECT) continue;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+	if (!(source->mode & PM_SOURCE_MODE_EXT_LIMIT)) continue;
+
+	// limit selection to some SN limit
+	assert (source->peak); // how can a source not have a peak?
+	if (source->peak->SN < SN_LIM) continue;
+
+	// limit selection by analysis region
+	if (source->peak->x < AnalysisRegion.x0) continue;
+	if (source->peak->y < AnalysisRegion.y0) continue;
+	if (source->peak->x > AnalysisRegion.x1) continue;
+	if (source->peak->y > AnalysisRegion.y1) continue;
+
+	// replace object in image
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	}
+	Next ++;
+
+	// if we request any of these measurements, we require the radial profile
+	if (doPetrosian || doIsophotal || doAnnuli || doKron) {
+	    if (!psphotRadialProfile (source, recipe, skynoise, maskVal)) {
+		// all measurements below require the radial profile; skip them all
+		// re-subtract the object, leave local sky
+		psTrace ("psphot", 5, "failed to extract radial profile for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+		pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+		continue;
+	    }
+	    source->mode |= PM_SOURCE_MODE_RADIAL_FLUX;
+	}
+
+	// Petrosian Mags
+	if (doPetrosian) {
+	    if (!psphotPetrosian (source, recipe, skynoise, maskVal)) {
+		psTrace ("psphot", 5, "measured petrosian flux & radius for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+	    } else {
+		psTrace ("psphot", 5, "measured petrosian flux & radius for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+		Npetro ++;
+		source->mode |= PM_SOURCE_MODE_EXTENDED_STATS;
+	    }
+	}
+
+# if (0)
+	// Isophotal Mags
+	if (doIsophotal) {
+	    if (!psphotIsophotal (source, recipe, maskVal)) {
+		psTrace ("psphot", 5, "failed to measure isophotal mags for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+	    } else {
+		psTrace ("psphot", 5, "measured isophotal mags for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+		Nisophot ++;
+		source->mode |= PM_SOURCE_MODE_EXTENDED_STATS;
+	    }
+	}
+	// Kron Mags
+	if (doKron) {
+	    if (!psphotKron (source, recipe, maskVal)) {
+		psTrace ("psphot", 5, "failed to measure kron mags for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+	    } else {
+		psTrace ("psphot", 5, "measure kron mags for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+		Nkron ++;
+		source->mode |= PM_SOURCE_MODE_EXTENDED_STATS;
+	    }
+	}
+# endif
+
+	// re-subtract the object, leave local sky
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+	if (source->extpars) {
+	    pmSourceRadialProfileFreeVectors(source->extpars->profile);
+	}
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot"), Next);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d petrosian\n", Npetro);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d isophotal\n", Nisophot);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d annuli\n", Nannuli);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d kron\n", Nkron);
+
+    psphotVisualShowResidualImage (readout);
+
+    if (doPetrosian) {
+	psphotVisualShowPetrosians (sources);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotExtendedSourceFits.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotExtendedSourceFits.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotExtendedSourceFits.c	(revision 27622)
@@ -0,0 +1,318 @@
+# include "psphotInternal.h"
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotExtendedSourceFits (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    // perform full extended source non-linear fits?
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS")) {
+        psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+        return true;
+    }
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotExtendedSourceFitsReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to fit extended sources for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// non-linear model fitting for extended sources
+bool psphotExtendedSourceFitsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status;
+    int Next = 0;
+    int Nconvolve = 0;
+    int NconvolvePass = 0;
+    int Nplain = 0;
+    int NplainPass = 0;
+    bool savePics = false;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+	return true;
+    }
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT"); // Mask value for bad pixels
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // select the collection of desired models
+    psMetadata *models = psMetadataLookupMetadata (&status, recipe, "EXTENDED_SOURCE_MODELS");
+    if (!status) {
+        psWarning ("extended source model fits requested but model model is missing (EXTENDED_SOURCE_MODELS)\n");
+        return true;
+    }
+    if (models->list->n == 0) {
+        psWarning ("extended source model fits requested but no models are specified\n");
+        return true;
+    }
+
+    // validate the model entries
+    psMetadataIterator *iter = psMetadataIteratorAlloc (models, PS_LIST_HEAD, NULL);
+    psMetadataItem *item = NULL;
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+
+      if (item->type != PS_DATA_METADATA) {
+        psAbort ("Invalid type for EXTENDED_SOURCE_MODEL entry %s, not a metadata folder", item->name);
+        // XXX we could cull the bad entries or build a validated model folder
+      }
+
+      psMetadata *model = (psMetadata *) item->data.md;
+
+      // check on the model type
+      char *modelName = psMetadataLookupStr (&status, model, "MODEL");
+      int modelType = pmModelClassGetType (modelName);
+      if (modelType < 0) {
+        psAbort ("Unknown model class for EXTENDED_SOURCE_MODEL entry %s: %s", item->name, modelName);
+      }
+      psMetadataAddS32 (model, PS_LIST_TAIL, "MODEL_TYPE", PS_META_REPLACE, "", modelType);
+
+      // check on the SNLIM, set a float value
+      char *SNword = psMetadataLookupStr (&status, model, "SNLIM");
+      if (!status) {
+        psAbort("SNLIM not defined for extended source model %s\n", item->name);
+      }
+      float SNlim = atof (SNword);
+      psMetadataAddF32 (model, PS_LIST_TAIL, "SNLIM_VALUE", PS_META_REPLACE, "", SNlim);
+
+      // check on the PSF-Convolution status
+      char *convolvedWord = psMetadataLookupStr (&status, model, "PSF_CONVOLVED");
+      if (!status || (strcasecmp (convolvedWord, "true") && strcasecmp (convolvedWord, "false"))) {
+        psAbort ("PSF_CONVOLVED entry invalid or missing for EXTENDED_SOURCE_MODEL entry %s", item->name);
+      }
+      bool convolved = !strcasecmp (convolvedWord, "true");
+      psMetadataAddBool (model, PS_LIST_TAIL, "PSF_CONVOLVED_VALUE", PS_META_REPLACE, "", convolved);
+    }
+    psFree (iter);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // what fraction of the PSF is used? (radius in pixels : 2 -> 5x5 box)
+    int psfSize = psMetadataLookupS32 (&status, recipe, "PCM_BOX_SIZE");
+    assert (status);
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // choose the sources of interest
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        // skip PSF-like and non-astronomical objects
+        if (source->type == PM_SOURCE_TYPE_STAR) continue;
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // XXX this should use peak?
+        if (source->peak->x < AnalysisRegion.x0) continue;
+        if (source->peak->y < AnalysisRegion.y0) continue;
+        if (source->peak->x > AnalysisRegion.x1) continue;
+        if (source->peak->y > AnalysisRegion.y1) continue;
+
+        // if model is NULL, we don't have a starting guess
+        // XXX use the parameters guessed from moments
+        // if (source->modelEXT == NULL) continue;
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+        Next ++;
+
+        // save the modelFlux here in case we need to subtract it (for failure)
+        psImage *modelFluxStart = psMemIncrRefCounter (source->modelFlux);
+
+        if (savePics) {
+          psphotSaveImage (NULL, readout->image, "image.xp.fits");
+        }
+
+        // array to store the pointers to the model flux images while the models are being fitted
+        psArray *modelFluxes = psArrayAllocEmpty (4);
+
+        // allocate the array to store the model fits
+        if (source->modelFits == NULL) {
+            source->modelFits = psArrayAllocEmpty (4);
+        }
+
+        // loop here over the models chosen for each source (exclude by S/N)
+        psMetadataIterator *iter = psMetadataIteratorAlloc (models, PS_LIST_HEAD, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+
+          // XXX this should have been forced above
+          assert (item->type == PS_DATA_METADATA);
+          psMetadata *model = (psMetadata *) item->data.md;
+
+          // check the SNlim and skip model if source is too faint
+          float SNlim = psMetadataLookupF32 (&status, model, "SNLIM_VALUE");
+          assert (status);
+
+          // limit selection to some SN limit
+          assert (source->peak); // how can a source not have a peak?
+          if (source->peak->SN < SNlim) continue;
+
+          // check on the model type
+          pmModelType modelType = psMetadataLookupS32 (&status, model, "MODEL_TYPE");
+          assert (status);
+
+          // check on the PSF-Convolution status
+          bool convolved = psMetadataLookupBool (&status, model, "PSF_CONVOLVED_VALUE");
+          assert (status);
+
+          // XXX psTraceSetLevel ("psLib.math.psMinimizeLMChi2", 6);
+          // XXX psTraceSetLevel ("psphot.psphotModelWithPSF_LMM", 6);
+
+          // fit the model as convolved or not
+          pmModel *modelFit = NULL;
+          if (convolved) {
+              modelFit = psphotPSFConvModel (readout, source, modelType, maskVal, markVal, psfSize);
+              if (!modelFit) {
+                  psTrace ("psphot", 5, "failed to fit psf-conv model for object at %f, %f", source->moments->Mx, source->moments->My);
+                  continue;
+              }
+              psTrace ("psphot", 4, "fit psf-conv model for %f, %f : %s chisq = %f\n", source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq);
+              Nconvolve ++;
+              if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
+                  NconvolvePass ++;
+		  source->mode |= PM_SOURCE_MODE_EXTENDED_FIT;
+              }
+          } else {
+              psFree (source->modelFlux);
+              source->modelFlux = NULL;
+              modelFit = psphotFitEXT (readout, source, modelType, maskVal, markVal);
+              if (!modelFit) {
+                  psTrace ("psphot", 5, "failed to fit plain model for object at %f, %f", source->moments->Mx, source->moments->My);
+                  continue;
+              }
+              pmSourceCacheModel (source, maskVal);
+              psTrace ("psphot", 4, "fit plain model for %f, %f : %s chisq = %f\n", source->moments->Mx, source->moments->My, pmModelClassGetName (modelFit->type), modelFit->chisq);
+              Nplain ++;
+              if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
+                  NplainPass ++;
+		  source->mode |= PM_SOURCE_MODE_EXTENDED_FIT;
+              }
+          }
+
+          // save each of the model flux images and store the best
+          psArrayAdd (modelFluxes, 4, source->modelFlux);
+
+          // test for fit quality / result
+          psArrayAdd (source->modelFits, 4, modelFit);
+
+          psFree (modelFit);
+        }
+        psFree (iter);
+
+        // evaluate the relative quality of the models, choose one
+        float minChisq = NAN;
+        int minModel = -1;
+        for (int i = 0; i < source->modelFits->n; i++) {
+            pmModel *model = source->modelFits->data[i];
+
+            if (!(model->flags & PM_MODEL_STATUS_FITTED)) continue;
+
+            if (model->flags & (PM_MODEL_STATUS_BADARGS)) continue;
+            if (model->flags & (PM_MODEL_STATUS_NONCONVERGE)) continue;
+            if (model->flags & (PM_MODEL_STATUS_OFFIMAGE)) continue;
+
+            if ((minModel < 0) || (model->chisq < minChisq)) {
+                minChisq = model->chisq;
+                minModel = i;
+            }
+        }
+
+        if (minModel == -1) {
+          // re-subtract the object, leave local sky
+          psTrace ("psphot", 5, "failed to fit extended source model to object at %f, %f", source->moments->Mx, source->moments->My);
+
+          // replace original model, subtract it
+          psFree (source->modelFlux);
+          source->modelFlux = modelFluxStart;
+
+          pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+          psFree (modelFluxes);
+
+          if (savePics) {
+              psphotSaveImage (NULL, readout->image, "image.xp.fits");
+              char key[10];
+              fprintf (stdout, "continue? ");
+              if (!fgets (key, 8, stdin)) {
+                  psWarning("Couldn't read anything.");
+              } else if (key[0] == 'n') {
+                  savePics = false;
+              }
+          }
+          continue;
+        }
+
+        // save the best extended model in modelEXT
+        psFree (source->modelEXT);
+        source->modelEXT = psMemIncrRefCounter (source->modelFits->data[minModel]);
+
+        // save the modelFlux for the best model
+        psFree (source->modelFlux);
+        source->modelFlux = psMemIncrRefCounter (modelFluxes->data[minModel]);
+
+        // subtract the best fit from the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+        // the initial model flux is no longer needed
+        psFree (modelFluxStart);
+        psFree (modelFluxes);
+
+        psTrace ("psphot", 4, "best ext model for %f, %f : %s chisq = %f\n", source->moments->Mx, source->moments->My, pmModelClassGetName (source->modelEXT->type), source->modelEXT->chisq);
+        psTrace ("psphot", 5, "extended source model for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
+
+        if (savePics) {
+          psphotSaveImage (NULL, readout->image, "image.xm.fits");
+          char key[10];
+          fprintf (stdout, "continue? ");
+          if (!fgets (key, 8, stdin)) {
+              psWarning("Couldn't read anything.");
+          } else if (key[0] == 'n') {
+              savePics = false;
+          }
+        }
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot"), Next);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d convolved models (%d passed)\n", Nconvolve, NconvolvePass);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d plain models (%d passed)\n", Nplain, NplainPass);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFake.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFake.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFake.c	(revision 27622)
@@ -0,0 +1,220 @@
+# include "psphotInternal.h"
+
+#define TESTING
+
+
+#ifdef TESTING
+#define MIN_FLUX 0.1                    // Minimum flux for faint sources
+#endif
+
+
+// Calculate the limiting magnitude for an image
+//
+// We limit ourselves to calculating the peak flux in the smoothed image, which should be close (modulo
+// non-Gaussian PSF) to the limiting flux in the un-smoothed original image.
+static bool fakeLimit(float *magLim,           // Limiting magntiude, to return
+                      float *minFlux,          // Minimum flux, to return
+                      const pmReadout *ro,     // Readout of interest
+                      float thresh,            // Threshold for source identification
+                      float xFWHM, float yFWHM, // Size of PSF
+                      float smoothNsigma,       // Smoothing limit
+                      psImageMaskType maskVal   // Value to mask
+                      );
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_VARIANCE(ro, false);
+    PS_ASSERT_METADATA_NON_NULL(recipe, false);
+
+    float smoothSigma  = 0.5*(FWHM_X + FWHM_Y) / (2.0*sqrtf(2.0*log(2.0)));
+    psKernel *kernel = psImageSmoothKernel(smoothSigma, smoothNsigma); // Kernel used for smoothing
+    float factor = psImageCovarianceCalculateFactor(kernel, readout->covariance); // Covariance matrix
+    psFree(kernel);
+
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for variance
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS);        // Random number generator
+    if (!psImageBackground(stats, NULL, ro->variance, ro->mask, maskVal, rng) ||
+        !isfinite(stats->robustMedian)) {
+        psError(PSPHOT_ERR_DATA, false, "Unable to determine mean variance");
+        psFree(stats);
+        psFree(rng);
+        return false;
+    }
+    psFree(rng);
+    float meanVar = stats->robustMedian; // Mean variance
+    psFree(stats);
+
+    if (magLim) {
+        *magLim = -2.5 * log10(thresh * sqrtf(meanVar * factor));
+    }
+    if (minFlux) {
+        int fudge = psMetadataLookupF32(NULL, recipe, "FAKE.MINFLUX"); // Fudge factor for minimum flux
+        *minFlux = sqrtf(meanVar) * fudge;
+    }
+
+    return true;
+}
+
+/// Generate a fake image and add it in to the existing readout
+static bool fakeGenerate(psImage **xSrc, psImage **ySrc, // Positions of sources
+                         const pmReadout *ro,            // Readout of interest
+                         const psVector *magOffsets,     // Magnitude offsets for fake sources
+                         int numSources,                 // Number of fake sources for each bin
+                         float refMag,                   // Reference magnitude
+                         float minFlux                   // Minimum flux level for fake image
+                         )
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PM_ASSERT_READOUT_IMAGE(ro, false);
+    PS_ASSERT_METADATA_NON_NULL(recipe, false);
+    PS_ASSERT_VECTOR_NON_NULL(magOffsets, false);
+    PS_ASSERT_VECTOR_TYPE(magOffsets, PS_TYPE_F32, false);
+
+    int numBins = magOffset->n;                                     // Number of bins
+    int numCols = ro->image->numCols, numRows = ro->image->numRows; // Size of image
+
+    *xSrc = psImageRecycle(*xSrc, numSources, numBins, PS_TYPE_F32);
+    *ySrc = psImageRecycle(*ySrc, numSources, numBins, PS_TYPE_F32);
+
+    // Master list, for image creation
+    psVector *xAll = psVectorAlloc(numBins * numSources, PS_TYPE_F32);
+    psVector *yAll = psVectorAlloc(numBins * numSources, PS_TYPE_F32);
+    psVector *magAll = psVectorAlloc(numBins * numSources, PS_TYPE_F32);
+
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS); // Random number generator
+    for (int i = 0; i <= numBins; i++) {
+        psArray *data = psArrayAlloc(3); // Sources for bin
+
+        psVector *x = psVectorAlloc(numSources, PS_TYPE_F32);
+        psVector *y = psVectorAlloc(numSources, PS_TYPE_F32);
+
+        float mag = refMag + magOffset->data.F32[i]; // Instrumental magnitude of sources
+
+        for (int j = 0; j <= numSources; j++) {
+            x->data.F32[j] = psRandomUniform(rng) * numCols;
+            y->data.F32[j] = psRandomUniform(rng) * numRows;
+            magAll->data.F32[i * numSources + j] = mag;
+        }
+        memcpy(xSrc->data.F32[i], x->data.F32, numSources * PSELEMTYPE_SIZEOF(PS_TYPE_F32));
+        memcpy(ySrc->data.F32[i], y->data.F32, numSources * PSELEMTYPE_SIZEOF(PS_TYPE_F32));
+        memcpy(&xAll->data.F32[i * numSources], x->data.F32, numSources * PSELEMTYPE_SIZEOF(PS_TYPE_F32));
+        memcpy(&yAll->data.F32[i * numSources], y->data.F32, numSources * PSELEMTYPE_SIZEOF(PS_TYPE_F32));
+    }
+    psFree(rng);
+
+    pmReadout *fakeRO = pmReadoutAlloc(); // Fake readout
+    if (!pmReadoutFakeFromVectors(fakeRO, xAll, yAll, mag, NULL, NULL, psf, minFlux, NAN, false, false)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate fake image");
+        psFree(fakeRO);
+        psFree(xAll);
+        psFree(yAll);
+        psFree(magAll);
+        return false;
+    }
+    psFree(xAll);
+    psFree(yAll);
+    psFree(magAll);
+
+    psBinaryOp(ro->image, ro->image, "+", fakeRO->image);
+    psFree(fakeRO);
+
+    return true;
+}
+
+
+// *** in this section, perform the photometry for fake sources ***
+bool psphotFake(pmConfig *config, pmReadout *readout, const pmPSF *psf,
+                const psMetadata *recipe, const psArray *realSources)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+    PM_ASSERT_READOUT_IMAGE(readout, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+    PS_ASSERT_METADATA_NON_NULL(recipe, false);
+    PS_ASSERT_ARRAY_NON_NULL(realSources, false);
+
+    psTimerStart("psphot.fake");
+
+    // Collect recipe information
+    float xFWHM = psMetadataLookupF32(NULL, recipe, "FWHM_X"); // PSF size in x
+    float yFWHM = psMetadataLookupF32(NULL, recipe, "FWHM_Y"); // PSF size in y
+    if (!isfinite(xFWHM) || !isfinite(yFWHM)) {
+        psError(PSPHOT_ERR_CONFIG, false, "Unable to find FWHM_X and FWHM_Y in recipe");
+        return false;
+    }
+    float smoothNsigma = psMetadataLookupF32(NULL, recipe, "PEAKS_SMOOTH_NSIGMA"); // Smoothing limit
+    if (!isfinite(smoothNsigma)) {
+        psError(PSPHOT_ERR_CONFIG, false, "Unable to find PEAKS_SMOOTH_NSIGMA in recipe");
+        return false;
+    }
+    float thresh = psMetadataLookupF32(NULL, recipe, "PEAKS_NSIGMA_LIMIT_2");
+    if (!isfinite(thresh)) {
+        psError(PSPHOT_ERR_CONFIG, false, "Unable to find PEAKS_NSIGMA_LIMIT_2 in recipe");
+        return false;
+    }
+    psVector *magOffsets = psMetadataLookupVector(NULL, recipe, "FAKE.MAG"); // Magnitude offsets
+    if (!magOffset || magOffset->type.type != PS_TYPE_F32) {
+        psError(PSPHOT_ERR_CONFIG, false, "Unable to find FAKE.MAG F32 vector in recipe");
+        return NULL;
+    }
+    int numSources = psMetadataLookupS32(NULL, recipe, "FAKE.NUM"); // Number of sources for each bin
+    if (numSources == 0) {
+        psError(PSPHOT_ERR_CONFIG, false, "Unable to find FAKE.NUM in recipe");
+        return NULL;
+    }
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Value to mask
+
+
+    // remove all sources, adding noise for subtracted sources
+    psphotRemoveAllSources(realSources, recipe);
+    psphotAddNoise(readout, realSources, recipe);
+
+    float magLim;                       // Guess at limiting magnitude
+    float minFlux;                      // Minimum flux for fake image
+    if (!fakeLimit(&magLim, &minFlux, readout, thresh, xFWHM, yFWHM, smoothNsigma, maskVal)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine limits for image");
+        return false;
+    }
+
+    psImage *xFake = NULL, *yFake = NULL; // Coordinates of sources, each bin in a row
+    if (!fakeGenerate(&xFake, &yFake, readout, magOffsets, numSources, magLim, minFlux)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate fake sources");
+        psFree(xFake);
+        psFree(yFake);
+        return false;
+    }
+
+    psImage *significance = psphotSignificanceImage(readout, recipe, 2, maskVal);
+    if (!significance) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate significance image");
+        psFree(xFake);
+        psFree(yFake);
+        return false;
+    }
+
+    int numBins = magOffsets->n;                          // Number of bins
+    psVector *frac = psVectorAlloc(numBins, PS_TYPE_S32); // Fraction of sources in each bin
+    for (int i = 0; i < numBins; i++) {
+        int numFound = 0;               // Number found
+        for (int j = 0; j < numSources; j++) {
+            float x = xFake->data.F32[i][j], y = yFake->data.F32[i][j]; // Coordinates of interest
+            int xPix = x, yPix = y;                                     // Pixel coordinates
+            if (significance->data.F32[yPix][xPix] > thresh) {
+                numFound++;
+            }
+        }
+        frac->data.S32[i] = (float)numFound / (float)numSources;
+    }
+
+    psFree(xFake);
+    psFree(yFake);
+    psFree(significance);
+
+    // Putting results on recipe because that appears to be the psphot standard, but it's not a good idea
+    psMetadataAddVector(readout->analysis, PS_LIST_TAIL, "FAKE.EFF", PS_META_REPLACE, "Efficiency fractions", frac);
+    psMetadataAddVector(readout->analysis, PS_LIST_TAIL, "FAKE.MAG", PS_META_REPLACE, "Efficiency magnitudes", magOffsets);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "FAKE.REF", PS_META_REPLACE, "Efficiency reference magnitude", magLim);
+
+    psFree(frac);
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFakeSources.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFakeSources.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFakeSources.c	(revision 27622)
@@ -0,0 +1,30 @@
+# include "psphotInternal.h"
+
+psArray *psphotFakeSources () {
+
+    // psphotUpdateHeader (header, config);
+
+    psArray *sources = psArrayAlloc (50);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = pmSourceAlloc ();
+        source->moments = pmMomentsAlloc ();
+        source->moments->Mx = 10;
+        source->moments->My = 10;
+        source->moments->Mxx = 1;
+        source->moments->Myy = 1;
+        source->moments->Mxy = 0;
+        source->moments->Sum = 1000;
+        source->moments->Peak = 100;
+        source->moments->Sky = 10;
+        source->moments->nPixels = 10;
+
+        source->peak = pmPeakAlloc (10, 10, 0, 0);
+        source->type = PM_SOURCE_TYPE_STAR;
+
+        pmModelType modelType = pmModelClassGetType ("PS_MODEL_QGAUSS");
+        source->modelPSF = pmSourceModelGuess (source, modelType);
+        sources->data[i] = source;
+    }
+    return sources;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindDetections.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindDetections.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindDetections.c	(revision 27622)
@@ -0,0 +1,127 @@
+# include "psphotInternal.h"
+
+// we store the detections on the readout->analysis for each readout this function finds new
+// peaks and new footprints.  any old peaks are saved on oldPeaks.  the resulting footprint set
+// contains all footprints (old and new)
+bool psphotFindDetections (pmConfig *config, const pmFPAview *view, bool firstPass)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotFindDetectionsReadout (config, view, "PSPHOT.INPUT", i, recipe, firstPass)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to find initial detections for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// smooth the image, search for peaks, optionally define footprints based on the peaks
+bool psphotFindDetectionsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool firstPass) {
+
+    bool status;
+    int pass;
+    float NSIGMA_PEAK = 25.0;
+    int NMAX = 0;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    psAssert (maskVal, "missing mask value?");
+
+    // Use the new pmFootprints approach?
+    const bool useFootprints = psMetadataLookupBool(NULL, recipe, "USE_FOOTPRINTS");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    // on the initial pass, detections have not yet been allocated or saved on readout->analysis
+    if (!detections) {
+	// create the container
+        detections = pmDetectionsAlloc();
+	
+	// save detections on the readout->analysis
+	if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot detections", detections)) {
+	    psError (PSPHOT_ERR_CONFIG, false, "problem saving detections on readout");
+	    return false;
+	}
+    } else {
+	psMemIncrRefCounter(detections); // so we can free the detections below
+    }
+
+    if (firstPass) {
+        pass = 1;
+        NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT"); PS_ASSERT (status, NULL);
+        NMAX = psMetadataLookupS32 (&status, recipe, "PEAKS_NMAX"); PS_ASSERT (status, NULL);
+    } else {
+        pass = 2;
+        NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT_2"); PS_ASSERT (status, NULL);
+        NMAX = 0; // unlimited number of peaks in final pass: allow a limit (PEAKS_NMAX_2) ?
+    }
+
+    float threshold = PS_SQR(NSIGMA_PEAK);
+
+    // move the old peaks array (if it exists) to oldPeaks XXX generically, we should be able
+    // to call this function an arbitrary number of times the old peaks are saved so they can
+    // be freed later -- the have to be freed after psphotFindFootprints is called below, since
+    // they are also owned by the oldFootprints, which are in turn merged into the new
+    // footprints.  (what about the source->peak entry?)
+    
+    assert (detections->oldPeaks == NULL);
+    detections->oldPeaks = detections->peaks;
+    detections->peaks = NULL;
+
+    // generate the smoothed significance image
+    psImage *significance = psphotSignificanceImage (readout, recipe, pass, maskVal);
+
+    // display the significance image
+    psphotVisualShowSignificance (significance, -1.0, PS_SQR(3.0*NSIGMA_PEAK));
+
+    // XXX getting some strange results from significance image
+    if (0) {
+	psImage *lsig = (psImage *) psUnaryOp (NULL, significance, "log");
+	psphotVisualShowSignificance (lsig, 0.0, 4.0);
+	psFree (lsig);
+    }	
+
+    // detect the peaks in the significance image
+    detections->peaks = psphotFindPeaks (significance, readout, recipe, threshold, NMAX);
+    psMetadataAddF32  (readout->analysis, PS_LIST_TAIL, "PEAK_THRESHOLD", PS_META_REPLACE, "Peak Detection Threshold", threshold);
+    if (!detections->peaks) {
+	// we only get a NULL peaks array due to a programming or config error. 
+	// this will result in a failure.
+	psFree (detections);
+	psError (PSPHOT_ERR_CONFIG, false, "failed on peak search");
+        return false;
+    }
+
+    // optionally merge peaks into footprints
+    if (useFootprints) {
+	psphotFindFootprints (detections, significance, readout, recipe, threshold, pass, maskVal);
+    }
+
+    psFree (significance);
+
+    // display the peaks and footprints
+    psphotVisualShowPeaks (detections);
+    psphotVisualShowFootprints (detections);
+
+    psFree (detections);
+
+    return true;
+}
+
+// if we use the footprints, the output peaks list contains both old and new peaks,
+// otherwise it only contains the new peaks.
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindFootprints.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindFootprints.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindFootprints.c	(revision 27622)
@@ -0,0 +1,77 @@
+# include "psphotInternal.h"
+
+bool psphotFindFootprints (pmDetections *detections, psImage *significance, pmReadout *readout, psMetadata *recipe, const float threshold, const int pass, psImageMaskType maskVal) {
+
+    bool status;
+
+    psTimerStart ("psphot.footprints");
+
+    int npixMin = psMetadataLookupS32(&status, recipe, "FOOTPRINT_NPIXMIN");
+    PS_ASSERT (status, false);
+
+    int growRadius = 0;
+    if (pass == 1) {
+        growRadius = psMetadataLookupS32(&status, recipe, "FOOTPRINT_GROW_RADIUS");
+    } else {
+        growRadius = psMetadataLookupS32(&status, recipe, "FOOTPRINT_GROW_RADIUS_2");
+    }
+    PS_ASSERT (status, false);
+
+    // find the raw footprints & assign the peaks to those footprints
+    psArray *footprints = pmFootprintsFind (significance, threshold, npixMin);
+
+    pmFootprintsAssignPeaks(footprints, detections->peaks);
+    // XXX handle the error conditions here
+
+    // footprints now owns the peaks; after culling (below), we will rebuild the peaks array
+    psFree (detections->peaks);
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "found %ld footprints: %f sec\n", footprints->n, psTimerMark ("psphot.footprints"));
+
+    // optionally grow footprints isotropically by growRadius pixels
+    if (growRadius > 0) {
+        bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading for psImageConvolve
+        psArray *tmp = pmFootprintArrayGrow(footprints, growRadius);
+        psImageConvolveSetThreads(oldThreads);
+        psLogMsg ("psphot", PS_LOG_MINUTIA, "grow footprint coverage by %d pixels, %ld footprints become %ld footprints: %f sec\n", growRadius, footprints->n, tmp->n, psTimerMark ("psphot.footprints"));
+        psFree(footprints);
+        footprints = tmp;
+    }
+
+    if (pass == 2) {
+        // merge in old peaks;
+        const int includePeaks = 0x1 | 0x2; // i.e. both new and old footprints
+
+        psArray *mergedFootprints = pmFootprintArraysMerge(detections->footprints, footprints, includePeaks);
+        psLogMsg ("psphot", PS_LOG_MINUTIA, "merged %ld new footprints with %ld existing ones: %f sec\n", footprints->n, (detections->footprints ? detections->footprints->n : 0), psTimerMark ("psphot.footprints"));
+
+        psFree(footprints);
+        psFree(detections->footprints);
+
+	// replace the merged footprints on the detection structure
+        detections->footprints = mergedFootprints;
+    } else {
+        detections->footprints = footprints;
+    }
+
+    psphotCullPeaks(readout->image, readout->variance, recipe, detections->footprints);
+    detections->peaks = pmFootprintArrayToPeaks(detections->footprints);
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks, %ld total footprints: %f sec\n", detections->peaks->n, detections->footprints->n, psTimerMark ("psphot.footprints"));
+
+    return detections;
+}
+
+bool psphotCheckFootprints (pmDetections *detections) {
+
+    // check for messed up footprints in the old peaks
+    for (int i = 0; i < detections->oldPeaks->n; i++) {
+	pmPeak *peak = detections->oldPeaks->data[i];
+	pmFootprint *footprint = peak->footprint;
+	if (!footprint) continue;
+	for (int j = 0; j < footprint->spans->n; j++) {
+	    pmSpan *sp = footprint->spans->data[j];
+	    psAssert (sp, "missing span");
+	}
+    }
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindPeaks.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindPeaks.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFindPeaks.c	(revision 27622)
@@ -0,0 +1,72 @@
+# include "psphotInternal.h"
+
+// Find peaks in the significance image above a threshold significance level.  The significance
+// image must be constructed to represent (S/N)^2.  If nMax is non-zero, only return a maximum
+// of nMax peaks
+psArray *psphotFindPeaks (psImage *significance, pmReadout *readout, psMetadata *recipe, const float threshold, const int nMax) {
+
+    bool status = false;
+
+    psTimerStart ("psphot.peaks");
+
+    // find the peaks in the smoothed image
+    psArray *peaks = pmPeaksInImage (significance, threshold);
+    if (peaks == NULL) {
+	// we only get a NULL peaks array due to a programming or config error. 
+	// this will result in a failure.
+        psError(PSPHOT_ERR_DATA, false, "no peaks found in this image");
+        return NULL;
+    }
+
+    if (peaks->n == 0) {
+        // XXX do we need to set something in the readout->analysis to indicate that
+        // we tried and failed to find peaks (something in the header data)
+	psLogMsg ("psphot", PS_LOG_INFO, "no peaks found in this image");
+	return peaks;
+    }
+
+    // Convert the peak values to S/N = sqrt(significance).
+    // Get the peak flux from the unsmoothed image.
+    // Rescale the peak position errors using the peak variance
+    // The peak pixel coords are guaranteed to be on the image
+    int row0 = readout->image->row0;
+    int col0 = readout->image->col0;
+    for (int i = 0; i < peaks->n; i++) {
+        pmPeak *peak = peaks->data[i];
+        peak->SN = sqrt(peak->value);
+        peak->flux = readout->image->data.F32[peak->y-row0][peak->x-col0];
+	// if (peak->flux / peak->value > 5.0/12.0) {
+	//     psWarning ("odd peak levels (1)");
+	// }
+	// if (peak->value / peak->flux > 5*12.0) {
+	//     psWarning ("odd peak levels (2)");
+	// }
+	if (readout->variance && isfinite (peak->dx)) {
+	    peak->dx *= sqrt(readout->variance->data.F32[peak->y-row0][peak->x-col0]);
+	}
+	if (readout->variance && isfinite (peak->dy)) {
+	    peak->dy *= sqrt(readout->variance->data.F32[peak->y-row0][peak->x-col0]);
+	}
+    }
+
+    // limit the total number of returned peaks as specified
+    psArraySort (peaks, pmPeakSortBySN);
+    if (nMax && (peaks->n > nMax)) {
+	psArray *tmpPeaks = psArrayAllocEmpty (nMax);
+	for (int i = 0; i < nMax; i++) {
+	    psArrayAdd (tmpPeaks, 100, peaks->data[i]);
+	}
+	psFree (peaks);
+	peaks = tmpPeaks;
+    }
+
+    // optional dump of all peak data 
+    char *output = psMetadataLookupStr (&status, recipe, "PEAKS_OUTPUT_FILE");
+    if (output && strcasecmp (output, "NONE")) {
+        pmPeaksWriteText (peaks, output);
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks: %f sec\n", peaks->n, psTimerMark ("psphot.peaks"));
+
+    return peaks;
+
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSet.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSet.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSet.c	(revision 27622)
@@ -0,0 +1,48 @@
+# include "psphotInternal.h"
+
+// This is only used by psphotModelTest.c
+bool psphotFitSet (pmSource *source, pmModel *oneModel, char *fitset, pmSourceFitMode mode, psImageMaskType maskVal) {
+
+    double x, y, Io;
+
+    FILE *f = fopen (fitset, "r");
+    if (f == NULL) return false;
+
+    psArray *modelSet = psArrayAllocEmpty (16);
+
+    while (fscanf (f, "%lf %lf %lf", &x, &y, &Io) == 3) {
+        pmModel *model = pmModelAlloc (oneModel->type);
+
+        for (psS32 i = 0; i < model->params->n; i++) {
+            model->params->data.F32[i] = oneModel->params->data.F32[i];
+            model->dparams->data.F32[i] = oneModel->dparams->data.F32[i];
+        }
+        model->params->data.F32[1] = Io;
+        model->params->data.F32[2] = x;
+        model->params->data.F32[3] = y;
+        psArrayAdd (modelSet, 16, model);
+    }
+
+    // XXX pmSourceFitSet must cache the modelFlux?
+    pmSourceFitSet (source, modelSet, mode, maskVal);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    for (int i = 0; i < modelSet->n; i++) {
+        pmModel *model = modelSet->data[i];
+        pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+
+        fprintf (stderr, "output parameters (obj %d):\n", i);
+        for (int n = 0; n < model->params->n; n++) {
+            fprintf (stderr, "%d : %f\n", n, model->params->data.F32[n]);
+        }
+    }
+
+    // write out
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+    return true;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSourcesLinear.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSourcesLinear.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSourcesLinear.c	(revision 27622)
@@ -0,0 +1,381 @@
+# include "psphotInternal.h"
+
+// fit flux (and optionally sky model) to all reasonable sources
+// with the linear fitting process.  sources must have an associated
+// model with selected pixels, and the fit radius must be defined
+
+// given the set of sources, each of which points to the pixels in the
+// science image, we construct a set of simulated sources with their own pixels.
+// these are used to determine the simultaneous linear fit of fluxes.
+// the analysis is performed wrt the simulated pixel values
+
+static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER, psImageMaskType markVal);
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotFitSourcesLinear (pmConfig *config, const pmFPAview *view, bool final)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+	// find the currently selected readout
+	pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", i); // File of interest
+	psAssert (file, "missing file?");
+
+	pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+	psAssert (readout, "missing readout?");
+
+	pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+	psAssert (detections, "missing detections?");
+
+	psArray *sources = detections->allSources;
+	psAssert (sources, "missing sources?");
+
+	pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+	psAssert (psf, "missing psf?");
+
+	if (!psphotFitSourcesLinearReadout (recipe, readout, sources, psf, final)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to fit sources (linear) for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotFitSourcesLinearReadout (psMetadata *recipe, pmReadout *readout, psArray *sources, pmPSF *psf, bool final) {
+
+    bool status;
+    float x;
+    float y;
+    float f;
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping linear fit");
+	return true;
+    }
+
+    psTimerStart ("psphot.linear");
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // source analysis is done in spatial order
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // storage array for fitSources
+    psArray *fitSources = psArrayAllocEmpty (sources->n);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionFromString (region);
+    AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    bool CONSTANT_PHOTOMETRIC_WEIGHTS =
+        psMetadataLookupBool(&status, recipe, "CONSTANT_PHOTOMETRIC_WEIGHTS");
+    if (!status) {
+        psAbort("You must provide a value for the BOOL recipe CONSTANT_PHOTOMETRIC_WEIGHTS");
+    }
+    int SKY_FIT_ORDER = psMetadataLookupS32(&status, recipe, "SKY_FIT_ORDER");
+    if (!status) {
+        SKY_FIT_ORDER = 0;
+    }
+    bool SKY_FIT_LINEAR = psMetadataLookupBool(&status, recipe, "SKY_FIT_LINEAR");
+    if (!status) {
+        SKY_FIT_LINEAR = false;
+    }
+
+    // XXX test: choose a larger-than expected radius:
+    float covarFactor = psImageCovarianceFactorForAperture(readout->covariance, 10.0); // Covariance matrix
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "covariance factor: %f\n", covarFactor);
+
+    // XXX do not apply covarFactor for the moment...
+    // covarFactor = 1.0;
+
+    // select the sources which will be used for the fitting analysis
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // turn this bit off and turn it on again if we pass this test
+        source->mode &= ~PM_SOURCE_MODE_LINEAR_FIT;
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // do not include CRs in the full ensemble fit
+        if (source->mode & PM_SOURCE_MODE_CR_LIMIT) continue;
+
+        if (final) {
+            if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) continue;
+        } else {
+            // if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+        }
+
+        // generate model for sources without, or skip if we can't
+        if (!source->modelFlux) {
+            if (!pmSourceCacheModel (source, maskVal)) continue;
+        }
+
+        // save the original coords
+        x = source->peak->xf;
+        y = source->peak->yf;
+
+        // is the source in the region of interest?
+        if (x < AnalysisRegion.x0) continue;
+        if (y < AnalysisRegion.y0) continue;
+        if (x > AnalysisRegion.x1) continue;
+        if (y > AnalysisRegion.y1) continue;
+
+        source->mode |= PM_SOURCE_MODE_LINEAR_FIT;
+        psArrayAdd (fitSources, 100, source);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built fitSources: %f sec (%ld objects)\n", psTimerMark ("psphot.linear"), sources->n);
+
+    if (fitSources->n == 0) {
+        return true;
+    }
+
+    // vectors to store stats for each object
+    // psVector *variance = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+    psVector *errors = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+
+    // create the border matrix (includes the sparse matrix)
+    // for just sky: 1 row; for x,y terms: 3 rows
+    psSparse *sparse = psSparseAlloc (fitSources->n, 100);
+    int nBorder = (SKY_FIT_ORDER == 0) ? 1 : 3;
+    psSparseBorder *border = psSparseBorderAlloc (sparse, nBorder);
+
+    // fill out the sparse matrix elements and border elements (B)
+    // SRCi is the current source of interest
+    // SRCj is a possibly overlapping source
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *SRCi = fitSources->data[i];
+
+        // diagonal elements of the sparse matrix (auto-cross-product)
+        f = pmSourceModelDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+        psSparseMatrixElement (sparse, i, i, f);
+
+        // the formal error depends on the weighting scheme
+        if (CONSTANT_PHOTOMETRIC_WEIGHTS) {
+            float var = pmSourceModelDotModel (SRCi, SRCi, false, covarFactor);
+            errors->data.F32[i] = 1.0 / sqrt(var);
+        } else {
+            errors->data.F32[i] = 1.0 / sqrt(f);
+        }
+
+
+        // find the image x model value
+        f = pmSourceDataDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+        psSparseVectorElement (sparse, i, f);
+
+        // add the per-source variances (border region)
+        switch (SKY_FIT_ORDER) {
+          case 1:
+            f = pmSourceModelWeight (SRCi, 1, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+            psSparseBorderElementB (border, i, 1, f);
+            f = pmSourceModelWeight (SRCi, 2, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+            psSparseBorderElementB (border, i, 2, f);
+
+          case 0:
+            f = pmSourceModelWeight (SRCi, 0, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+            psSparseBorderElementB (border, i, 0, f);
+            break;
+
+          default:
+            psAbort("Invalid SKY_FIT_ORDER %d\n", SKY_FIT_ORDER);
+            break;
+        }
+
+        // loop over all other stars following this one
+        for (int j = i + 1; j < fitSources->n; j++) {
+            pmSource *SRCj = fitSources->data[j];
+
+            // skip over disjoint source images, break after last possible overlap
+            if (SRCi->pixels->row0 + SRCi->pixels->numRows < SRCj->pixels->row0) break;
+            if (SRCj->pixels->row0 + SRCj->pixels->numRows < SRCi->pixels->row0) continue;
+            if (SRCi->pixels->col0 + SRCi->pixels->numCols < SRCj->pixels->col0) continue;
+            if (SRCj->pixels->col0 + SRCj->pixels->numCols < SRCi->pixels->col0) continue;
+
+            // got an overlap; calculate cross-product and add to output array
+            f = pmSourceModelDotModel (SRCi, SRCj, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+            psSparseMatrixElement (sparse, j, i, f);
+        }
+    }
+
+    psSparseResort (sparse);
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built matrix: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    // set the sky, sky_x, sky_y components of border matrix
+    SetBorderMatrixElements (border, readout, fitSources, CONSTANT_PHOTOMETRIC_WEIGHTS, SKY_FIT_ORDER, markVal);
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = 0.0;
+    constraint.paramMax   = 1e8;
+    constraint.paramDelta = 1e8;
+
+    // solve for normalization terms (need include local sky?)
+    psVector *norm = NULL;
+    psVector *skyfit = NULL;
+    if (SKY_FIT_LINEAR) {
+        psSparseBorderSolve (&norm, &skyfit, constraint, border, 5);
+        psLogMsg ("psphot", PS_LOG_MINUTIA, "skyfit: %f\n", skyfit->data.F32[0]);
+    } else {
+        norm = psSparseSolve (NULL, constraint, sparse, 5);
+        skyfit = NULL;
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "solve matrix: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    // XXXX **** philosophical question:
+    // we measure bright objects in three passes: 1) linear fit; 2) non-linear fit; 3) linear fit:
+    // should retain the chisq and errors from the intermediate non-linear fit?
+    // the non-linear fit provides better values for the position errors, and for
+    // extended sources, the shape errors
+
+    // adjust I0 for fitSources and subtract
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *source = fitSources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+
+        // assign linearly-fitted normalization
+        if (isnan(norm->data.F32[i])) {
+            psAbort("linear fitted source is nan");
+        }
+
+        model->params->data.F32[PM_PAR_I0] = norm->data.F32[i];
+        model->dparams->data.F32[PM_PAR_I0] = errors->data.F32[i];
+        // XXX is the value of 'errors' modified by the sky fit?
+
+        // subtract object
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "sub models: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    // measure chisq for each source
+    // for (int i = 0; final && (i < fitSources->n); i++) {
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *source = fitSources->data[i];
+        if (source->mode & PM_SOURCE_MODE_NONLINEAR_FIT) continue;
+        pmModel *model = pmSourceGetModel (NULL, source);
+        pmSourceChisq (model, source->pixels, source->maskObj, source->variance, maskVal, covarFactor);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "get chisqs: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    // psFree (index);
+    psFree (sparse);
+    psFree (fitSources);
+    psFree (norm);
+    psFree (skyfit);
+    psFree (errors);
+    psFree (border);
+
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot.linear"));
+
+    psphotVisualShowResidualImage (readout);
+    psphotVisualPlotChisq (sources);
+    // psphotVisualShowFlags (sources);
+
+    // We have to place this visualization here because the models are not realized until
+    // psphotGuessModels or fitted until psphotFitSourcesLinear.
+    psphotVisualShowPSFStars (recipe, psf, sources);
+
+    return true;
+}
+
+// XXX do we need this?
+// XXX disallow the simultaneous sky fit and remove this code...
+
+// Calculate the weight terms for the sky fit component of the matrix.  This function operates
+// on the pixels which correspond to all of the sources of interest.  These elements fill in
+// the border matrix components in the sparse matrix equation.
+static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER, psImageMaskType markVal) {
+
+    // generate the image-wide weight terms
+    // turn on MARK for all image pixels
+    psRegion fullArray = psRegionSet (0, 0, 0, 0);
+    fullArray = psRegionForImage (readout->mask, fullArray);
+    psImageMaskRegion (readout->mask, fullArray, "OR", markVal);
+
+    // turn off MARK for all object pixels
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL) continue;
+        float x = model->params->data.F32[PM_PAR_XPOS];
+        float y = model->params->data.F32[PM_PAR_YPOS];
+        psImageMaskCircle (source->maskView, x, y, model->fitRadius, "AND", PS_NOT_IMAGE_MASK(markVal));
+    }
+
+    // accumulate the image statistics from the masked regions
+    psF32 **image  = readout->image->data.F32;
+    psF32 **variance = readout->variance->data.F32;
+    psImageMaskType  **mask   = readout->mask->data.PS_TYPE_IMAGE_MASK_DATA;
+
+    double w, x, y, x2, xy, y2, xc, yc, wt, f, fo, fx, fy;
+    w = x = y = x2 = xy = y2 = fo = fx = fy = 0;
+
+    int col0 = readout->image->col0;
+    int row0 = readout->image->row0;
+
+    for (int j = 0; j < readout->image->numRows; j++) {
+        for (int i = 0; i < readout->image->numCols; i++) {
+            if (mask[j][i]) continue;
+            if (constant_weights) {
+                wt = 1.0;
+            } else {
+                wt = variance[j][i];
+            }
+            f = image[j][i];
+            w   += 1/wt;
+            fo  += f/wt;
+            if (SKY_FIT_ORDER == 0) continue;
+
+            xc  = i + col0;
+            yc  = j + row0;
+            x  +=    xc/wt;
+            y  +=    yc/wt;
+            x2 += xc*xc/wt;
+            xy += xc*yc/wt;
+            y2 += yc*yc/wt;
+            fx +=  f*xc/wt;
+            fy +=  f*yc/wt;
+        }
+    }
+
+    // turn off MARK for all image pixels
+    psImageMaskRegion (readout->mask, fullArray, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+    // set the Border T elements
+    psSparseBorderElementG (border, 0, fo);
+    psSparseBorderElementT (border, 0, 0, w);
+    if (SKY_FIT_ORDER > 0) {
+        psSparseBorderElementG (border, 0, fx);
+        psSparseBorderElementG (border, 0, fy);
+        psSparseBorderElementT (border, 1, 0, x);
+        psSparseBorderElementT (border, 2, 0, y);
+        psSparseBorderElementT (border, 0, 1, x);
+        psSparseBorderElementT (border, 1, 1, x2);
+        psSparseBorderElementT (border, 2, 1, xy);
+        psSparseBorderElementT (border, 0, 2, y);
+        psSparseBorderElementT (border, 1, 2, xy);
+        psSparseBorderElementT (border, 2, 2, y2);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSourcesLinearStack.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSourcesLinearStack.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotFitSourcesLinearStack.c	(revision 27622)
@@ -0,0 +1,328 @@
+# include "psphotInternal.h"
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotFitSourcesLinearStack (pmConfig *config, const pmFPAview *view, bool final)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotFitSourcesLinearReadoutStack (config, view, "PSPHOT.INPUT", i, final)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to fit sources (linear) for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotFitSourcesLinearReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, bool final) {
+
+    bool status;
+    float x;
+    float y;
+    float f;
+    // float r;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping linear fit");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (sources, "missing psf?");
+
+    psTimerStart ("psphot.linear");
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // source analysis is done in spatial order
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // storage array for fitSources
+    psArray *fitSources = psArrayAllocEmpty (sources->n);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionFromString (region);
+    AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    bool CONSTANT_PHOTOMETRIC_WEIGHTS =
+        psMetadataLookupBool(&status, recipe, "CONSTANT_PHOTOMETRIC_WEIGHTS");
+    if (!status) {
+        psAbort("You must provide a value for the BOOL recipe CONSTANT_PHOTOMETRIC_WEIGHTS");
+    }
+    int SKY_FIT_ORDER = psMetadataLookupS32(&status, recipe, "SKY_FIT_ORDER");
+    if (!status) {
+        SKY_FIT_ORDER = 0;
+    }
+    bool SKY_FIT_LINEAR = psMetadataLookupBool(&status, recipe, "SKY_FIT_LINEAR");
+    if (!status) {
+        SKY_FIT_LINEAR = false;
+    }
+
+    // XXX test: choose a larger-than expected radius:
+    float covarFactor = psImageCovarianceFactorForAperture(readout->covariance, 10.0); // Covariance matrix
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "covariance factor: %f\n", covarFactor);
+
+    // XXX do not apply covarFactor for the moment...
+    // covarFactor = 1.0;
+
+    // select the sources which will be used for the fitting analysis
+    for (int i = 0; i < objects->n; i++) {
+        pmPhotObj *object = objects->data[i];
+	if (!object) continue;
+	if (!object->sources) continue;
+
+	// check an element of the group to see if we should use it
+	if (!object->flags & PM_PHOT_OBJ_BAD) continue;
+
+	for (int j = 0; j < object->sources->n; j++) {
+	  pmSource *source = object->sources->data[j];
+	  if (!source) continue;
+
+	  // turn this bit off and turn it on again if we keep this source
+	  source->mode &= ~PM_SOURCE_MODE_LINEAR_FIT;
+
+	  // generate model for sources without, or skip if we can't
+	  if (!source->modelFlux) {
+            if (!pmSourceCacheModel (source, maskVal)) continue;
+	  }
+
+	  source->mode |= PM_SOURCE_MODE_LINEAR_FIT;
+	  psArrayAdd (fitSources, 100, source);
+	}
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built fitSources: %f sec (%ld objects)\n", psTimerMark ("psphot.linear"), sources->n);
+
+    if (fitSources->n == 0) {
+        return true;
+    }
+
+    // vectors to store stats for each object
+    // psVector *variance = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+    psVector *errors = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+
+    // create the sparse matrix
+    psSparse *sparse = psSparseAlloc (fitSources->n, 100);
+
+    // fill out the sparse matrix elements and border elements (B)
+    // SRCi is the current source of interest
+    // SRCj is a possibly overlapping source
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *SRCi = fitSources->data[i];
+
+        // diagonal elements of the sparse matrix (auto-cross-product)
+        f = pmSourceModelDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+        psSparseMatrixElement (sparse, i, i, f);
+
+        // the formal error depends on the weighting scheme
+        if (CONSTANT_PHOTOMETRIC_WEIGHTS) {
+            float var = pmSourceModelDotModel (SRCi, SRCi, false, covarFactor);
+            errors->data.F32[i] = 1.0 / sqrt(var);
+        } else {
+            errors->data.F32[i] = 1.0 / sqrt(f);
+        }
+
+        // find the image x model value
+        f = pmSourceDataDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+        psSparseVectorElement (sparse, i, f);
+
+        // loop over all other stars following this one
+        for (int j = i + 1; j < fitSources->n; j++) {
+            pmSource *SRCj = fitSources->data[j];
+
+	    // XXX I need to know if this source is on the same image as SRCi --
+	    if (!sameImge) { continue; }
+
+            // skip over disjoint source images, break after last possible overlap
+            if (SRCi->pixels->row0 + SRCi->pixels->numRows < SRCj->pixels->row0) break;
+            if (SRCj->pixels->row0 + SRCj->pixels->numRows < SRCi->pixels->row0) continue;
+            if (SRCi->pixels->col0 + SRCi->pixels->numCols < SRCj->pixels->col0) continue;
+            if (SRCj->pixels->col0 + SRCj->pixels->numCols < SRCi->pixels->col0) continue;
+
+            // got an overlap; calculate cross-product and add to output array
+            f = pmSourceModelDotModel (SRCi, SRCj, CONSTANT_PHOTOMETRIC_WEIGHTS, covarFactor);
+            psSparseMatrixElement (sparse, j, i, f);
+        }
+    }
+
+    psSparseResort (sparse);
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built matrix: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = 0.0;
+    constraint.paramMax   = 1e8;
+    constraint.paramDelta = 1e8;
+
+    // solve for normalization terms (need include local sky?)
+    psVector *norm = NULL;
+    norm = psSparseSolve (NULL, constraint, sparse, 5);
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "solve matrix: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    // XXXX **** philosophical question:
+    // we measure bright objects in three passes: 1) linear fit; 2) non-linear fit; 3) linear fit:
+    // should retain the chisq and errors from the intermediate non-linear fit?
+    // the non-linear fit provides better values for the position errors, and for
+    // extended sources, the shape errors
+
+    // adjust I0 for fitSources and subtract
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *source = fitSources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+
+        // assign linearly-fitted normalization
+        if (isnan(norm->data.F32[i])) {
+            psAbort("linear fitted source is nan");
+        }
+
+        model->params->data.F32[PM_PAR_I0] = norm->data.F32[i];
+        model->dparams->data.F32[PM_PAR_I0] = errors->data.F32[i];
+        // XXX is the value of 'errors' modified by the sky fit?
+
+        // subtract object
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "sub models: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    // measure chisq for each source
+    // for (int i = 0; final && (i < fitSources->n); i++) {
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *source = fitSources->data[i];
+        if (source->mode & PM_SOURCE_MODE_NONLINEAR_FIT) continue;
+        pmModel *model = pmSourceGetModel (NULL, source);
+        pmSourceChisq (model, source->pixels, source->maskObj, source->variance, maskVal, covarFactor);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "get chisqs: %f sec (%d elements)\n", psTimerMark ("psphot.linear"), sparse->Nelem);
+
+    // psFree (index);
+    psFree (sparse);
+    psFree (fitSources);
+    psFree (norm);
+    psFree (skyfit);
+    psFree (errors);
+    psFree (border);
+
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot.linear"));
+
+    psphotVisualShowResidualImage (readout);
+    psphotVisualPlotChisq (sources);
+    // psphotVisualShowFlags (sources);
+
+    // We have to place this visualization here because the models are not realized until
+    // psphotGuessModels or fitted until psphotFitSourcesLinear.
+    psphotVisualShowPSFStars (recipe, psf, sources);
+
+    return true;
+}
+
+// XXX do we need this?
+// XXX disallow the simultaneous sky fit and remove this code...
+
+// Calculate the weight terms for the sky fit component of the matrix.  This function operates
+// on the pixels which correspond to all of the sources of interest.  These elements fill in
+// the border matrix components in the sparse matrix equation.
+static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER, psImageMaskType markVal) {
+
+    // generate the image-wide weight terms
+    // turn on MARK for all image pixels
+    psRegion fullArray = psRegionSet (0, 0, 0, 0);
+    fullArray = psRegionForImage (readout->mask, fullArray);
+    psImageMaskRegion (readout->mask, fullArray, "OR", markVal);
+
+    // turn off MARK for all object pixels
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL) continue;
+        float x = model->params->data.F32[PM_PAR_XPOS];
+        float y = model->params->data.F32[PM_PAR_YPOS];
+        psImageMaskCircle (source->maskView, x, y, model->fitRadius, "AND", PS_NOT_IMAGE_MASK(markVal));
+    }
+
+    // accumulate the image statistics from the masked regions
+    psF32 **image  = readout->image->data.F32;
+    psF32 **variance = readout->variance->data.F32;
+    psImageMaskType  **mask   = readout->mask->data.PS_TYPE_IMAGE_MASK_DATA;
+
+    double w, x, y, x2, xy, y2, xc, yc, wt, f, fo, fx, fy;
+    w = x = y = x2 = xy = y2 = fo = fx = fy = 0;
+
+    int col0 = readout->image->col0;
+    int row0 = readout->image->row0;
+
+    for (int j = 0; j < readout->image->numRows; j++) {
+        for (int i = 0; i < readout->image->numCols; i++) {
+            if (mask[j][i]) continue;
+            if (constant_weights) {
+                wt = 1.0;
+            } else {
+                wt = variance[j][i];
+            }
+            f = image[j][i];
+            w   += 1/wt;
+            fo  += f/wt;
+            if (SKY_FIT_ORDER == 0) continue;
+
+            xc  = i + col0;
+            yc  = j + row0;
+            x  +=    xc/wt;
+            y  +=    yc/wt;
+            x2 += xc*xc/wt;
+            xy += xc*yc/wt;
+            y2 += yc*yc/wt;
+            fx +=  f*xc/wt;
+            fy +=  f*yc/wt;
+        }
+    }
+
+    // turn off MARK for all image pixels
+    psImageMaskRegion (readout->mask, fullArray, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+    // set the Border T elements
+    psSparseBorderElementG (border, 0, fo);
+    psSparseBorderElementT (border, 0, 0, w);
+    if (SKY_FIT_ORDER > 0) {
+        psSparseBorderElementG (border, 0, fx);
+        psSparseBorderElementG (border, 0, fy);
+        psSparseBorderElementT (border, 1, 0, x);
+        psSparseBorderElementT (border, 2, 0, y);
+        psSparseBorderElementT (border, 0, 1, x);
+        psSparseBorderElementT (border, 1, 1, x2);
+        psSparseBorderElementT (border, 2, 1, xy);
+        psSparseBorderElementT (border, 0, 2, y);
+        psSparseBorderElementT (border, 1, 2, xy);
+        psSparseBorderElementT (border, 2, 2, y2);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotForced.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotForced.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotForced.c	(revision 27622)
@@ -0,0 +1,35 @@
+# include "psphotStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();                  // register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotForcedArguments (argc, argv);
+    assert(config);
+
+    psphotVersionPrint();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotForcedImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedArguments.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedArguments.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedArguments.c	(revision 27622)
@@ -0,0 +1,199 @@
+# include "psphotStandAlone.h"
+
+static void writeHelpInfo(const char* program, pmConfig* config, FILE* ofile)
+{
+  fprintf(ofile,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "%s -help\n"
+	  "\n"
+	  "%s -version\n"
+	  "\n"
+	  "where:\n"
+	  "  FileNameList is a text file containing filenames, one per line\n"
+	  "  MaskFileNameList is a text file of mask filenames, one per line\n"
+	  "  VarFileNameList is a text file of variance filenames, one per line\n"
+	  "  OutFileBaseName is the 'root name' for output files\n"
+	  "\n"
+	  "additional options:\n"
+	  "  -psf PsfFile1[,PsfFile2,...] or -psflist PsfFileNameList\n"
+	  "     specify PSF rather than letting %s estimate it\n"
+	  "  -src SrcFile1[,SrcFile2,...] or -srclist SrcFileNameList\n"
+	  "     specify additional sources for PSF generation\n"
+	  "  -chip nn[,nn,...]\n"
+	  "     select detector chips to process; default is all.\n"
+	  "     Indices correspond to zero-based offset in the FPA metadata table.\n"
+	  "  -photcode PhotoCodeName\n"
+	  "     specify photocode\n"
+	  "  -region RegionString\n"
+	  "     specify analysis region.  String is of form '[x0:x1,y0:y1]'\n"
+	  "     To use this option you must define a default in psphot.config\n"
+	  "  -visual\n"
+	  "     turns on interactive display mode\n"
+	  "  -dumpconfig CfgFileName\n"
+          "     causes config info to be dumped to the named file.\n"
+	  "  -break NOTHING|BACKMDL|PEAKS|MOMENTS|PSFMODEL|ENSEMBLE|PASS1\n"
+	  "     choose a point at which to exit processing early\n"
+	  "  -nthreads n\n"
+	  "     set number of parallel threads of execution\n"
+	  "  -F OldFileRule ReplacementFileRule\n"
+	  "     change file naming rule; e.g. '-F PSPHOT.OUTPUT PSPHOT.OUT.CMF.MEF'\n"
+	  "  -D name stringval\n"
+	  "     set a string-valued config parameter\n"
+	  "  -Di name intval\n"
+	  "     set an integer-valued config parameter\n"
+	  "  -Df name fval\n"
+	  "     set a float-valued config parameter\n"
+	  "  -Db name boolval\n"
+	  "     set a boolean-valued config parameter\n"
+	  "  -v, -vv, -vvv\n"
+	  "     set increasing levels of verbosity\n"
+	  "  -logfmt FormatString\n"
+	  "     set format string used for log messages\n"
+	  "  -trace Fac Lvl\n"
+	  "     set tracing for facility Fac to integer Lvl, e.g. '-trace err 10'\n"
+	  "  -trace-levels\n"
+	  "     print current trace levels\n",
+	  program,program,program,program,program);
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_SUCCESS);
+}
+
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  pmConfig *config,      // Configuration
+		  int exitCode
+		  ) 
+{
+  fprintf(stderr,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "Try '%s -help' for more options and explanation\n",
+	  program,program,program);
+    if (exitCode != PS_EXIT_SUCCESS)
+      psErrorStackPrint(stderr, "Error reading arguments\n");
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(exitCode);
+}
+
+pmConfig *psphotForcedArguments(int argc, char **argv) {
+
+    int N;
+    bool status, status1, status2;
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+      psErrorStackPrint(stderr, "Can't read site configuration");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    PSARGUMENTS_INSTANTIATE_GENERICS( psphot, config, argc, argv );
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // Number of threads is handled
+    PSARGUMENTS_INSTANTIATE_THREADSARG( psphot, config, argc, argv )
+
+    // visual : interactive display mode
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    //
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",       "-mask",     "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "VARIANCE",   "-variance", "-variancelist");
+
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf",      "-psflist");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "No psf model is supplied (use -psf)");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    status1 = pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC", "-src", "-srclist");
+    status2 = pmConfigFileSetsMD (config->arguments, &argc, argv, "SRCTEXT", "-srctext", "-srctextlist");
+    
+    if (!status1 && !status2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "No source list is supplied (use one of -src, -srctext, -srclist, or -srctextlist)");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (psArgumentGet(argc, argv, "-help") ||
+	psArgumentGet(argc, argv, "-h"))
+      writeHelpInfo(argv[0], config, stdout);
+      
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotForcedArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedImageLoop.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedImageLoop.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedImageLoop.c	(revision 27622)
@@ -0,0 +1,129 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotForcedImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *load = psMetadataLookupPtr (&status, config->files, "PSPHOT.LOAD");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+        return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmHDU *lastHDU = NULL;              // Last HDU updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // for psphot, we force data to be read at the chip level
+    while ((chip = pmFPAviewNextChip (view, load->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (! chip->process || ! chip->file_exists) { continue; }
+
+        // load just the input image data (image, mask, weight)
+        pmFPAfileActivate (config->files, false, NULL);
+        pmFPAfileActivate (config->files, true, "PSPHOT.LOAD");
+        pmFPAfileActivate (config->files, true, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, true, "PSPHOT.VARIANCE");
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+        // mosaic the cells of a chip into a single contiguous (trimmed) chip
+        if (!psphotMosaicChip(config, view, "PSPHOT.INPUT", "PSPHOT.LOAD")) ESCAPE ("Unable to mosaic chip.");
+
+        // try to load other supporting data (PSF, SRC, etc).
+        // do not re-load the following three files
+        pmFPAfileActivate (config->files, true, NULL);
+        pmFPAfileActivate (config->files, false, "PSPHOT.LOAD");
+        pmFPAfileActivate (config->files, false, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, false, "PSPHOT.VARIANCE");
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+        // re-activate files so they will be closed and freed below
+        pmFPAfileActivate (config->files, true, NULL);
+
+        // there is now only a single chip (multiple readouts?). loop over it and process
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                psLogMsg ("psphot", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (! readout->data_exists) { continue; }
+
+                // Update the header
+		pmHDU *hdu = pmHDUGetHighest(input->fpa, chip, cell);
+		if (hdu && hdu != lastHDU) {
+		    psphotVersionHeaderFull(hdu->header);
+		    lastHDU = hdu;
+                }
+
+		// if an external mask is supplied, ensure that NAN pixels are also masked
+		if (readout->mask) {
+		    psImageMaskType maskSat = pmConfigMaskGet("SAT", config); // Mask value for saturated pixels
+		    if (!pmReadoutMaskNonfinite(readout, maskSat)) {
+			psError(psErrorCodeLast(), false, "Unable to mask non-finite pixels.");
+			psFree(view);
+			return false;
+		    }
+		}
+
+                // run the actual photometry analysis on this chip/cell/readout
+                if (!psphotForcedReadout (config, view)) {
+                    psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+                    psFree (view);
+                    return false;
+                }
+            }
+
+            status = true;
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL.STDEV");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+            if (!status) {
+                psError(PSPHOT_ERR_PROG, false, "trouble dropping internal files");
+                psFree (view);
+                return false;
+            }
+        }
+
+        // save output which is saved at the chip level
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed output for Chip in psphot.");
+    }
+    // save output which is saved at the fpa level
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed ouput for FPA in psphot.");
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
+// PSPHOT.MASK
+// PSPHOT.VARIANCE
+//
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedReadout.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedReadout.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotForcedReadout.c	(revision 27622)
@@ -0,0 +1,89 @@
+# include "psphotInternal.h"
+
+bool psphotForcedReadout(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
+    // for psphot envisions threading within psphotReadout, not multiple threads calling
+    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
+    // jointly by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    // allow objects to be fit with ugly models (central holes, extreme asymmetry, etc)
+    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX);
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (config, view)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    PS_ASSERT_PTR_NON_NULL (breakPt, false);
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view);
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, view);
+    }
+
+    if (!psphotLoadPSF (config, view)) {
+    	// this only happens if we had a programming error in psphotLoadPSF
+        psError (PSPHOT_ERR_UNKNOWN, false, "error loading psf model");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // include externally-supplied sources
+    psphotLoadExtSources (config, view);
+
+    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
+    psphotGuessModels (config, view);
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view);
+
+    // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
+    psphotFitSourcesLinear (config, view, false);
+
+    // identify CRs and extended sources
+    // XXX do I want to do this step?
+    // XXX do I want to / need to calculate the moments?
+    // psphotSourceSize (config, readout, sources, recipe, psf, 0);
+
+    // calculate source magnitudes
+    psphotMagnitudes(config, view);
+
+    // XXX do I want to do this?
+    // if (!psphotEfficiency(config, readout, view, psf, recipe, sources)) {
+    //     psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
+    //     psErrorClear();
+    // }
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (config, view);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, view);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotGuessModels.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotGuessModels.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotGuessModels.c	(revision 27622)
@@ -0,0 +1,226 @@
+# include "psphotInternal.h"
+
+// XXX : the threading here is not great.  this may be due to blocks between elements, but
+// the selection of the objects in a cell is not optimal.  To fix:
+// 1) define the boundaries of the cells up front
+// 2) loop over the sources once and associate them with their cell
+// 3) define the threaded function to work with sources for a given cell
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotGuessModels (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotGuessModelsReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to guess models for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// construct an initial PSF model for each object (new sources only)
+bool psphotGuessModelsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index) {
+
+    bool status;
+
+    psTimerStart ("psphot.models");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+        psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping model guess");
+        return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // setup the PSF fit radius details
+    psphotInitRadiusPSF (recipe, readout->analysis, psf->type);
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_GUESS_MODEL");
+            psArrayAdd(job->args, 1, readout);
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            psArrayAdd(job->args, 1, psf);
+
+            // XXX change these to use abstract mask type info
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,  PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, markVal,  PS_TYPE_IMAGE_MASK);
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                psFree (job);
+                return false;
+            }
+            psFree(job);
+        }
+
+        // wait for the threads to finish and manage results
+        // wait here for the threaded jobs to finish
+        // fprintf (stderr, "wait for threads (%d, %d)\n", jx, jy);
+        if (!psThreadPoolWait (false)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            // we have no returned data from this operation
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
+    }
+
+    // XXX check that all sources that should have models
+    int nMiss = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->tmpFlags & PM_SOURCE_TMPF_MODEL_GUESS) {
+            continue;
+        }
+        nMiss ++;
+    }
+    psAssert (nMiss == 0, "failed to attempt to build models for %d objects\n", nMiss);
+
+    psFree (cellGroups);
+
+    psLogMsg ("psphot.models", 4, "built models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.models"));
+    return true;
+}
+
+// construct models only for sources in the specified region
+bool psphotGuessModel_Threaded (psThreadJob *job) {
+
+    pmReadout *readout = job->args->data[0];
+    psArray *sources   = job->args->data[1];
+    pmPSF *psf         = job->args->data[2];
+
+    psImageMaskType maskVal = PS_SCALAR_VALUE(job->args->data[3],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType markVal = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA);
+
+    int nSrc = 0;
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // this is used to mark sources for which the model is measured. We check later that
+        // all are used.
+        source->tmpFlags |= PM_SOURCE_TMPF_MODEL_GUESS;
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+        if (!source->peak) continue;
+
+        nSrc ++;
+
+        // the guess central intensity comes from the peak:
+        float Io = source->peak->flux;
+
+        // We have two options to get a guess for the object position: the position from the
+        // peak and the position from the moments.  Use the peak position if (a) there are no
+        // moments and (b) the sources is not saturated
+
+        bool useMoments = false;
+        useMoments = (source->mode & PM_SOURCE_MODE_SATSTAR);  // we only want to try if SATSTAR is set, but..
+        useMoments = (useMoments && source->moments);          // can't if there are no moments
+        useMoments = (useMoments && source->moments->nPixels); // can't if the moments were not measured
+        useMoments = (useMoments && !(source->mode && PM_SOURCE_MODE_MOMENTS_FAILURE)); // can't if the moments failed...
+
+        float Xo, Yo;
+        if (useMoments) {
+            Xo = source->moments->Mx;
+            Yo = source->moments->My;
+        } else {
+            Xo = source->peak->xf;
+            Yo = source->peak->yf;
+        }
+
+        // set PSF parameters for this model (apply 2D shape model to coordinates Xo, Yo)
+        pmModel *modelPSF = pmModelFromPSFforXY(psf, Xo, Yo, Io);
+
+        if (modelPSF == NULL) {
+            psWarning ("Failed to determine PSF model at (%f,%f); trying image center", Xo, Yo);
+
+            float Xc = 0.5*readout->image->numCols;
+            float Yc = 0.5*readout->image->numRows;
+            pmModel *modelPSF = pmModelFromPSFforXY(psf, Xc, Yc, Io);
+            if (modelPSF == NULL) {
+                psError(PSPHOT_ERR_PSF, false, "Failed to determine PSF model at center of image");
+                return false;
+            }
+
+            // Now set the object position at the expected location:
+            modelPSF->params->data.F32[PM_PAR_XPOS] = Xo;
+            modelPSF->params->data.F32[PM_PAR_YPOS] = Yo;
+            source->mode |= PM_SOURCE_MODE_BADPSF;
+        }
+
+        // set the fit radius based on the object flux limit and the model
+        // this function affects the mask pixels
+        psphotCheckRadiusPSF (readout, source, modelPSF, markVal);
+
+        // set the source PSF model
+        psAssert (source->modelPSF == NULL, "failed to free one of the models?");
+        source->modelPSF = modelPSF;
+        source->modelPSF->residuals = psf->residuals;
+
+        pmSourceCacheModel (source, maskVal);  // ALLOC x14 (!)
+    }
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotImageLoop.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotImageLoop.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotImageLoop.c	(revision 27622)
@@ -0,0 +1,129 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *load = psMetadataLookupPtr (&status, config->files, "PSPHOT.LOAD");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+        return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmHDU *lastHDU = NULL;              // Last HDU updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // for psphot, we force data to be read at the chip level
+    while ((chip = pmFPAviewNextChip (view, load->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (! chip->process || ! chip->file_exists) { continue; }
+
+        // load just the input image data (image, mask, weight)
+        pmFPAfileActivate (config->files, false, NULL);
+        pmFPAfileActivate (config->files, true, "PSPHOT.LOAD");
+        pmFPAfileActivate (config->files, true, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, true, "PSPHOT.VARIANCE");
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+        // mosaic the cells of a chip into a single contiguous (trimmed) chip
+        if (!psphotMosaicChip(config, view, "PSPHOT.INPUT", "PSPHOT.LOAD")) ESCAPE ("Unable to mosaic chip.");
+
+        // try to load other supporting data (PSF, SRC, etc).
+        // do not re-load the following three files
+        pmFPAfileActivate (config->files, true, NULL);
+        pmFPAfileActivate (config->files, false, "PSPHOT.LOAD");
+        pmFPAfileActivate (config->files, false, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, false, "PSPHOT.VARIANCE");
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+        // re-activate files so they will be closed and freed below
+        pmFPAfileActivate (config->files, true, NULL);
+
+        // there is now only a single chip (multiple readouts?). loop over it and process
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                psLogMsg ("psphot", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (! readout->data_exists) { continue; }
+
+                // Update the header
+		pmHDU *hdu = pmHDUGetHighest(input->fpa, chip, cell);
+		if (hdu && hdu != lastHDU) {
+		    psphotVersionHeaderFull(hdu->header);
+		    lastHDU = hdu;
+                }
+
+		// if an external mask is supplied, ensure that NAN pixels are also masked
+		if (readout->mask) {
+		    psImageMaskType maskSat = pmConfigMaskGet("SAT", config); // Mask value for saturated pixels
+		    if (!pmReadoutMaskNonfinite(readout, maskSat)) {
+			psError(psErrorCodeLast(), false, "Unable to mask non-finite pixels.");
+			psFree(view);
+			return false;
+		    }
+		}
+
+                // run the actual photometry analysis on this chip/cell/readout
+                if (!psphotReadout (config, view)) {
+                    psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+                    psFree (view);
+                    return false;
+                }
+            }
+
+            status = true;
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL.STDEV");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+            if (!status) {
+                psError(PSPHOT_ERR_PROG, false, "trouble dropping internal files");
+                psFree (view);
+                return false;
+            }
+        }
+
+        // save output which is saved at the chip level
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed output for Chip in psphot.");
+    }
+    // save output which is saved at the fpa level
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed ouput for FPA in psphot.");
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
+// PSPHOT.MASK
+// PSPHOT.VARIANCE
+//
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotImageQuality.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotImageQuality.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotImageQuality.c	(revision 27622)
@@ -0,0 +1,307 @@
+#include "psphotInternal.h"
+#define USE_SAMPLE 1 /* sample vs robust stats */
+
+// XXX Lots of code duplication here
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotImageQuality (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotImageQualityReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on to measure image quality for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// selecting the 'good' stars (likely to be psf stars), measure the M_cn, M_sn terms for n = 2,3,4
+bool psphotImageQualityReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
+{
+    bool status = true;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // if we have a PSF, skip this analysis
+    if (psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF")) {
+	return true;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping image quality");
+	return true;
+    }
+
+    psVector *FWHM_MAJOR = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *FWHM_MINOR = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    psVector *M2 = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *M3 = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *M4 = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    psVector *M2c = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *M2s = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    int num = 0;                        // Number of good sources
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        if (!source) {
+            continue;
+        }
+
+        // select by S/N?
+        // select by PSFSTAR mode?
+        // ??
+        if (source->type != PM_SOURCE_TYPE_STAR || !(source->mode & PM_SOURCE_MODE_PSFSTAR)) {
+            psTrace("psphot", 10, "Ignoring source for image quality because not a good star");
+            continue;
+        }
+
+        pmMoments *moments = source->moments;
+        if (!moments) {
+            psTrace("psphot", 10, "Ignoring source for image quality because no moments");
+            continue;
+        }
+
+        // if (source->moments->SN < XXX) continue;
+
+        // M_cn = \sum (f r^2 cos(n t)) / \sum (f)
+        // M_sn = \sum (f r^2 sin(n t)) / \sum (f)
+
+        // r^2 cos(2t) = r^2 cos^2 t - r^2 sin^2 t = x^2 - y^2
+        // r^2 sin(2t) = r^2 2 cos t sin t = 2 x y
+
+        // r^2 cos(3t) = r^2 cos^3 t - r^2 3 cos t sin^2 t = (x^3 - 3 x y^2) / r
+        // r^2 sin(3t) = r^2 3 cos^2 t sin t - sin^3 t = (3 x^2 y - y^3) / r
+
+        // r^2 cos(4t) = r^2 cos^4 t - r^2 6 cos^2 t sin^2 t + r^2 sin^4 t = (x^4 - 6 x^2 y^2 + y^4) / r^2
+        // r^2 sin(4t) = r^2 4 cos^3 t sin t - 4 sin^3 t cos t = (4 x^3 y - 4 y^3 x) / r^2
+
+        num++;
+
+        psEllipseMoments emoments;
+        emoments.x2 = moments->Mxx;
+        emoments.xy = moments->Mxy;
+        emoments.y2 = moments->Myy;
+        psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+
+        psVectorAppend (FWHM_MAJOR, 2.355*axes.major);
+        psVectorAppend (FWHM_MINOR, 2.355*axes.minor);
+
+        // the moments->Mnnn terms contain the straight sums: for example: moments->Mxxx contains \sum x^3 / r
+        float M_c2 = moments->Mxx - moments->Myy;
+        float M_s2 = 2*moments->Mxy;
+        psVectorAppend (M2, hypot(M_c2, M_s2));
+        psVectorAppend (M2c, M_c2);
+        psVectorAppend (M2s, M_s2);
+
+        float M_c3 = moments->Mxxx - 3.0*moments->Mxyy;
+        float M_s3 = 3.0*moments->Mxxy - moments->Myyy;
+        psVectorAppend (M3, hypot (M_c3, M_s3));
+
+        float M_c4 = moments->Mxxxx - 6.0*moments->Mxxyy + moments->Myyyy;
+        float M_s4 = 4.0*moments->Mxxxy - 4.0*moments->Mxyyy;
+        psVectorAppend (M4, hypot (M_c4, M_s4));
+    }
+
+    if (num == 0) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no valid sources for image quality, skipping");
+	return true;
+    }
+
+    psMetadataAddS32(readout->analysis, PS_LIST_TAIL, "IQ_NSTAR", PS_META_REPLACE, "Number of stars used for IQ measurements", M2->n);
+
+// XXX make this a recipe option
+#if (USE_SAMPLE)
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV | PS_STAT_SAMPLE_QUARTILE);
+
+    if (!psVectorStats(stats, FWHM_MAJOR, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+
+    float fwhm_major = stats->sampleMean;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1",   PS_META_REPLACE, "FWHM of Major Axis from moments", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1_E", PS_META_REPLACE, "FWHM scatter (Major) from moments", stats->sampleStdev);
+
+    if (!psVectorStats(stats, FWHM_MINOR, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float fwhm_minor = stats->sampleMean;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2",   PS_META_REPLACE, "FWHM of Minor Axis from moments", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2_E", PS_META_REPLACE, "FWHM scatter (Minor) from moments", stats->sampleStdev);
+
+    if (!psVectorStats(stats, M2, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float vM2 = stats->sampleMean;
+    float dM2 = stats->sampleStdev;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2",    PS_META_REPLACE, "M_2 = sqrt (M_c2^2 + M_s2^2)", vM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_ER", PS_META_REPLACE, "Stdev of M_2", dM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_LQ", PS_META_REPLACE, "Lower Quartile of M_2", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_UQ", PS_META_REPLACE, "Upper Quartile of M_2", stats->sampleUQ);
+
+    if (!psVectorStats(stats, M2c, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C",   PS_META_REPLACE, "M_2c = sum f r^2 cos(2phi) / sum f", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_E", PS_META_REPLACE, "Stdev of M_2c", stats->sampleStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_L", PS_META_REPLACE, "Lower Quartile of M_2c", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_U", PS_META_REPLACE, "Upper Quartile of M_2c", stats->sampleUQ);
+
+    if (!psVectorStats(stats, M2s, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S",   PS_META_REPLACE, "M_2s = sum f r^2 cos(2phi) / sum f", stats->sampleMean);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_E", PS_META_REPLACE, "Stdev of M_2s", stats->sampleStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_L", PS_META_REPLACE, "Lower Quartile of M_2s", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_U", PS_META_REPLACE, "Upper Quartile of M_2s", stats->sampleUQ);
+
+    if (!psVectorStats(stats, M3, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float vM3 = stats->sampleMean;
+    float dM3 = stats->sampleStdev;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3",   PS_META_REPLACE, "M_3 = sqrt (M_c3^2 + M_s3^2)", vM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_ER", PS_META_REPLACE, "Stdev of M_3", dM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_LQ", PS_META_REPLACE, "Lower Quartile of M_3", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_UQ", PS_META_REPLACE, "Upper Quartile of M_3", stats->sampleUQ);
+
+    if (!psVectorStats(stats, M4, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float vM4 = stats->sampleMean;
+    float dM4 = stats->sampleStdev;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4",   PS_META_REPLACE, "M_4 = sqrt (M_c4^2 + M_s4^2)", vM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_ER", PS_META_REPLACE, "Stdev of M_4", dM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_LQ", PS_META_REPLACE, "Lower Quartile of M_4", stats->sampleLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_UQ", PS_META_REPLACE, "Upper Quartile of M_4", stats->sampleUQ);
+
+#else
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV | PS_STAT_ROBUST_QUARTILE);
+
+    if (!psVectorStats(stats, FWHM_MAJOR, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float fwhm_major = stats->robustMedian;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1",   PS_META_REPLACE, "FWHM of Major Axis from moments", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW1_E", PS_META_REPLACE, "FWHM scatter (Major) from moments", stats->robustStdev);
+
+    if (!psVectorStats(stats, FWHM_MINOR, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float fwhm_minor = stats->robustMedian;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2",   PS_META_REPLACE, "FWHM of Minor Axis from moments", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_FW2_E", PS_META_REPLACE, "FWHM scatter (Minor) from moments", stats->robustStdev);
+
+    if (!psVectorStats(stats, M2, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float vM2 = stats->robustMedian;
+    float dM2 = stats->robustStdev;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2",    PS_META_REPLACE, "M_2 = sqrt (M_c2^2 + M_s2^2)", vM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_ER", PS_META_REPLACE, "Stdev of M_2", dM2);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_LQ", PS_META_REPLACE, "Lower Quartile of M_2", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2_UQ", PS_META_REPLACE, "Upper Quartile of M_2", stats->robustUQ);
+
+    if (!psVectorStats(stats, M2c, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C",   PS_META_REPLACE, "M_2c = sum f r^2 cos(2phi) / sum f", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_E", PS_META_REPLACE, "Stdev of M_2c", stats->robustStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_L", PS_META_REPLACE, "Lower Quartile of M_2c", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2C_U", PS_META_REPLACE, "Upper Quartile of M_2c", stats->robustUQ);
+
+    if (!psVectorStats(stats, M2s, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S",   PS_META_REPLACE, "M_2s = sum f r^2 cos(2phi) / sum f", stats->robustMedian);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_E", PS_META_REPLACE, "Stdev of M_2s", stats->robustStdev);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_L", PS_META_REPLACE, "Lower Quartile of M_2s", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M2S_U", PS_META_REPLACE, "Upper Quartile of M_2s", stats->robustUQ);
+
+    if (!psVectorStats(stats, M3, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float vM3 = stats->robustMedian;
+    float dM3 = stats->robustStdev;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3",   PS_META_REPLACE, "M_3 = sqrt (M_c3^2 + M_s3^2)", vM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_ER", PS_META_REPLACE, "Stdev of M_3", dM3);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_LQ", PS_META_REPLACE, "Lower Quartile of M_3", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M3_UQ", PS_META_REPLACE, "Upper Quartile of M_3", stats->robustUQ);
+
+    if (!psVectorStats(stats, M4, NULL, NULL, 0)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "Unable to perform statistics to measure image quality");
+        goto FAIL;
+    }
+    float vM4 = stats->robustMedian;
+    float dM4 = stats->robustStdev;
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4",   PS_META_REPLACE, "M_4 = sqrt (M_c4^2 + M_s4^2)", vM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_ER", PS_META_REPLACE, "Stdev of M_4", dM4);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_LQ", PS_META_REPLACE, "Lower Quartile of M_4", stats->robustLQ);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "IQ_M4_UQ", PS_META_REPLACE, "Upper Quartile of M_4", stats->robustUQ);
+#endif
+
+    psLogMsg ("psphot", PS_LOG_INFO, "Image Quality Stats from %ld psf stars : FWHM (major, minor) [pixels]: %f, %f\n",
+              M2->n, fwhm_major, fwhm_minor);
+
+    psLogMsg ("psphot", PS_LOG_INFO, "M_2 : %f +/- %f, M_3 : %f +/- %f, M_4 : %f +/- %f  [pixels^n]\n",
+              vM2, dM2, vM3, dM3, vM4, dM4);
+
+    psFree(FWHM_MAJOR);
+    psFree(FWHM_MINOR);
+    psFree(M2);
+    psFree(M2c);
+    psFree(M2s);
+    psFree(M3);
+    psFree(M4);
+    psFree(stats);
+
+    return true;
+
+ FAIL:
+    psFree(FWHM_MAJOR);
+    psFree(FWHM_MINOR);
+    psFree(M2);
+    psFree(M2c);
+    psFree(M2s);
+    psFree(M3);
+    psFree(M4);
+    psFree(stats);
+
+    return false;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotInternal.h
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotInternal.h	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotInternal.h	(revision 27622)
@@ -0,0 +1,17 @@
+/* this file is included by the internal, library functions. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef PSPHOT_INTERNAL_H
+#define PSPHOT_INTERNAL_H
+
+#include <stdio.h>
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+#endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotIsophotal.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotIsophotal.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotIsophotal.c	(revision 27622)
@@ -0,0 +1,78 @@
+# include "psphotInternal.h"
+
+bool psphotIsophotal (pmSource *source, psMetadata *recipe, psImageMaskType maskVal) {
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  bool status;
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *flux = source->extpars->profile->flux;
+
+  // flux at which to measure isophotal parameters
+  // XXX ISOPHOTAL_FLUX should be specified in mags, need the zero point to get counts/sec
+  float ISOPHOT_FLUX = psMetadataLookupF32 (&status, recipe, "ISOPHOTAL_FLUX");
+  assert (status);
+
+  // find the first bin below the flux level and the last above the level
+  // XXX can this be done faster with bisection?
+  // XXX do I need to worry about crazy outliers?
+  // XXX should i be smoothing or fitting the curve?
+  int firstBelow = -1;
+  int lastAbove = -1;
+  for (int i = 0; i < flux->n; i++) {
+    if (flux->data.F32[i] > ISOPHOT_FLUX) lastAbove = i;
+    if ((firstBelow < 0) && (flux->data.F32[i] < ISOPHOT_FLUX)) firstBelow = i;
+  }
+  // if we don't go out far enough, we have a problem...
+  if (lastAbove == flux->n - 1) {
+    psTrace ("psphot", 5, "did not go out far enough to reach isophotal magnitude");
+    // XXX raise a flag ?
+    return false;
+  }
+  if (firstBelow < 0) {
+    psTrace ("psphot", 5, "did not go out far enough to bound isophotal magnitude: error unmeasured");
+    // XXX raise a flag ?
+    lastAbove = firstBelow;
+    return false;
+  }
+
+  // need to examine pixels in this vicinity
+  float isophotalFluxFirst = 0;
+  float isophotalFluxLast = 0;
+  for (int i = 0; i <= PS_MAX(firstBelow, lastAbove); i++) {
+    if (i <= firstBelow) {
+      isophotalFluxFirst += flux->data.F32[i];
+    }
+    if (i <= lastAbove) {
+      isophotalFluxLast += flux->data.F32[i];
+    }
+  }
+  float isophotalFlux    = 0.5*(isophotalFluxLast + isophotalFluxFirst);
+  float isophotalFluxErr = 0.5*fabs(isophotalFluxLast - isophotalFluxFirst);
+
+  float isophotalRad     = 0.5*(radius->data.F32[firstBelow] + radius->data.F32[lastAbove]);
+  float isophotalRadErr  = 0.5*fabs(radius->data.F32[firstBelow] - radius->data.F32[lastAbove]);
+
+  if (!source->extpars->isophot) {
+    source->extpars->isophot = pmSourceIsophotalValuesAlloc ();
+  }
+
+  // these are uncalibrated: instrumental mags and pixel units
+  source->extpars->isophot->mag    = -2.5*log10(isophotalFlux);
+  source->extpars->isophot->magErr = isophotalFluxErr / isophotalFlux;
+
+  source->extpars->isophot->rad    = isophotalRad;
+  source->extpars->isophot->radErr = isophotalRadErr;
+
+  psTrace ("psphot", 5, "Isophot flux:%f +/- %f @ %f +/- %f for %f, %f\n",
+           source->extpars->isophot->mag, source->extpars->isophot->magErr,
+           source->extpars->isophot->rad, source->extpars->isophot->radErr,
+           source->peak->xf, source->peak->yf);
+
+  return true;
+
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotKernelFromPSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotKernelFromPSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotKernelFromPSF.c	(revision 27622)
@@ -0,0 +1,45 @@
+# include "psphotInternal.h"
+
+psKernel *psphotKernelFromPSF (pmSource *source, int nPix) {
+
+    assert (source);
+    assert (source->psfImage); // XXX build if needed?
+
+    int x0 = source->peak->xf - source->psfImage->col0;
+    int y0 = source->peak->yf - source->psfImage->row0;
+
+    // need to decide on the size: dynamically? statically?
+    psKernel *psf = psKernelAlloc (-nPix, +nPix, -nPix, +nPix);
+
+    // XXX we should just re-construct a PSF at this location 
+    // psModelAdd (psf->image, NULL, source->modelPSF, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM | PM_MODEL_OP_CENTER);
+  
+    // if the realized PSF for this object does not cover the full kernel, give up for now
+    if (x0 + psf->xMin < 0) goto escape;
+    if (x0 + psf->xMax >= source->psfImage->numCols) goto escape;
+    if (y0 + psf->yMin < 0) goto escape;
+    if (y0 + psf->yMax >= source->psfImage->numRows) goto escape;
+
+    double sum = 0.0;
+    for (int j = psf->yMin; j <= psf->yMax; j++) {
+	for (int i = psf->xMin; i <= psf->xMax; i++) {
+	    double value = source->psfImage->data.F32[y0 + j][x0 + i];
+	    psf->kernel[j][i] = value;
+	    sum += value;
+	}
+    }
+    assert (sum > 0.0);
+
+    // psf must be normalized (integral = 1.0)
+    for (int i = 0; i < psf->image->numRows; i++) {
+	for (int j = 0; j < psf->image->numCols; j++) {
+	    psf->image->data.F32[i][j] /= sum;
+	}
+    }
+
+    return psf;
+
+escape:
+    psFree (psf);
+    return NULL;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotKron.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotKron.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotKron.c	(revision 27622)
@@ -0,0 +1,8 @@
+# include "psphotInternal.h"
+
+bool psphotKron (pmSource *source, psMetadata *recipe, psImageMaskType maskVal) {
+
+  psLogMsg ("psphot", PS_LOG_INFO, "not implemented\n");
+  return true;
+
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotLoadPSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotLoadPSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotLoadPSF.c	(revision 27622)
@@ -0,0 +1,79 @@
+# include "psphotInternal.h"
+
+// NOTE : pmPSF_IO.c functions must load the psf model onto the chip->analysis metadata because
+// the I/O operation likely occurs before the readout exists.  This implementation assumes that
+// a single psf model is valid for the entire set of readouts (not valid for a time series of readouts)
+
+// XXX for now (2010.01.27), the supporting programs do not define multiple PSPHOT.PSF.LOAD
+// files to go with multiple PSPHOT.INPUT files.  as a result, the implementation below is
+// currently going to work for the case of a single input file, but will fail if we try with a
+// stack of images.
+
+// load an externally supplied psf model
+bool psphotLoadPSFReadout (pmConfig *config, const pmFPAview *view, const char *outFilename, const char *inFilename, int index) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmFPAfile *inFile = pmFPAfileSelectSingle(config->files, inFilename, index); // File of interest
+    if (inFile == NULL) {
+        psLogMsg ("psphot", 3, "no psf supplied for this chip");
+        return true;
+    }
+
+    // find the currently selected readout
+    pmFPAfile *outFile = pmFPAfileSelectSingle(config->files, outFilename, index); // File of interest
+    if (outFile == NULL) {
+        psLogMsg ("psphot", 3, "no psf supplied for this chip");
+        return true;
+    }
+
+    // find the currently selected chip
+    pmChip *chip = pmFPAviewThisChip (view, inFile->fpa);
+    if (!chip) return false;
+
+    // find the currently selected readout
+    pmReadout *readout = pmFPAviewThisReadout (view, outFile->fpa);
+    if (!readout) return false;
+
+    // check if a PSF model is supplied by the user
+    pmPSF *psf = psMetadataLookupPtr (&status, chip->analysis, "PSPHOT.PSF");
+    if (psf == NULL) {
+        psLogMsg ("psphot", 3, "no psf supplied for this chip");
+        return true;
+    }
+
+    if (!psphotPSFstats (readout, psf)) {
+        psAbort("cannot measure PSF shape terms");
+    }
+
+    psLogMsg ("psphot", 3, "using externally supplied PSF model for this readout");
+
+    // save PSF on readout->analysis
+    if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot psf model", psf)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "problem saving sources on readout");
+        return false;
+    }
+
+    return true;
+}
+
+bool psphotLoadPSF (pmConfig *config, const pmFPAview *view) {
+
+    bool status = false;
+
+    // XXX PSPHOT.PSF.LOAD vs PSPHOT.INPUT -- see note at top
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+        // Generate the mask and weight images, including the user-defined analysis region of interest
+        if (!psphotLoadPSFReadout (config, view, "PSPHOT.INPUT", "PSPHOT.PSF.LOAD", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to load PSF model for PSPHOT.PSF.LOAD entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotLoadSRCTEXT.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotLoadSRCTEXT.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotLoadSRCTEXT.c	(revision 27622)
@@ -0,0 +1,135 @@
+# include "psphotInternal.h"
+
+bool psphotLoadSRCTEXT (pmFPA *fpa, pmConfig *config) {
+
+    pmChip *chip;
+
+    // we have a psArray of filenames, but there is no information to tie the filenames to the
+    // corresponding chip/cell/readout.  Fortunately: (a) psphot only really operates on chips
+    // and (b) we can make some basic assumptions:
+
+    // 1) if there is only a single entry in the array, read the file and attach the results to
+    // each of the chip->analysis metadata entries (just an extra ref counter).
+
+    // 2) if there are multiple entries, the number of filenames must match the number of
+    // chips.
+
+    psArray *files = psMetadataLookupPtr(NULL, config->arguments, "SRCTEXT");
+
+    // XXX is this allowed?  how do we get an empty entry in arguments?
+    if (!files->n) return true;
+
+    // we use a default model type of GAUSS to define the sources
+    pmModelType modelType = pmModelClassGetType("PS_MODEL_GAUSS");
+
+    psArray *sourceArrays = psArrayAllocEmpty(files->n);
+    for (int i = 0; i < files->n; i++) {
+
+	char *filename = files->data[i];
+
+	// each file contains a list of X Y coordinates and nothing else?
+	FILE *f = fopen (filename, "r");
+	if (f == NULL) {
+            psError(PSPHOT_ERR_CONFIG, true, "Failed to read source text list %s", filename);
+	    return false;
+	}
+
+	double X, Y;
+
+	psArray *sources = psArrayAllocEmpty (100);
+	while (fscanf (f, "%lf %lf", &X, &Y) != EOF) {
+
+	    psEllipseAxes axes;
+	    
+	    pmSource *source = pmSourceAlloc ();
+	    pmModel *model = pmModelAlloc (modelType);
+	    source->modelPSF  = model;
+	    source->type = PM_SOURCE_TYPE_STAR;
+
+	    // NOTE: A SEGV here because "model" is NULL is probably caused by not initialising the models.
+	    psF32 *PAR = model->params->data.F32;
+	    psF32 *dPAR = model->dparams->data.F32;
+
+	    // NOTE: most of these values are irrelevant for loaded source positions
+	    source->seq       = 0;
+	    PAR[PM_PAR_XPOS]  = X;
+	    PAR[PM_PAR_YPOS]  = Y;
+	    dPAR[PM_PAR_XPOS] = 0.0;
+	    dPAR[PM_PAR_YPOS] = 0.0;
+	    axes.major        = 1.0;
+	    axes.minor        = 1.0;
+	    axes.theta        = 0.0;
+
+	    PAR[PM_PAR_SKY]   = 0.0;
+	    dPAR[PM_PAR_SKY]  = 0.0;
+	    source->sky       = PAR[PM_PAR_SKY];
+	    source->skyErr    = dPAR[PM_PAR_SKY];
+
+	    source->psfMag    = 0.0;
+	    source->errMag    = 0.0;
+	    source->apMag     = 0.0;
+
+	    PAR[PM_PAR_I0]    = 1.0;
+	    dPAR[PM_PAR_I0]   = 0.0;
+
+	    pmPSF_AxesToModel (PAR, axes);
+
+	    float peakFlux    = 1.0;
+
+	    source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], peakFlux, PM_PEAK_LONE);
+	    source->peak->flux = peakFlux;
+	    source->peak->dx   = dPAR[PM_PAR_XPOS];
+	    source->peak->dy   = dPAR[PM_PAR_YPOS];
+	    source->peak->xf   = PAR[PM_PAR_XPOS]; // the alloc function takes the pixel index,
+	    source->peak->yf   = PAR[PM_PAR_YPOS]; // but we know the pixel coordinate
+
+	    source->pixWeight = 1.0;
+	    source->crNsigma  = 0.0;
+	    source->extNsigma = 0.0;
+	    source->apRadius  = 0.0;
+
+	    model->chisq      = 0.0;
+	    model->nDOF       = 0;
+	    model->nPix       = 0;
+
+	    source->moments = pmMomentsAlloc ();
+	    source->moments->Mxx = 0.0;
+	    source->moments->Mxy = 0.0;
+	    source->moments->Myy = 0.0;
+
+	    source->mode = 0;
+	    
+	    psArrayAdd (sources, 100, source);
+	    psFree(source);
+	}
+	psArrayAdd (sourceArrays, 100, sources);
+	psFree (sources);
+    }
+
+    if (files->n == 1) {
+	psArray *sources = sourceArrays->data[0];
+	pmFPAview *view = pmFPAviewAlloc (0);
+	while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+	    if (!chip->process) { continue; }
+	    psMetadataAddPtr (chip->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES.TEXT", PS_DATA_ARRAY | PS_META_REPLACE, "loaded source from textfile", sources);
+	}
+	psFree (view);
+    } else {
+	int nSrc = 0;
+	pmFPAview *view = pmFPAviewAlloc (0);
+	while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+	    if (!chip->process) { continue; }
+
+	    if (nSrc >= sourceArrays->n) {
+		psError(PSPHOT_ERR_CONFIG, true, "mismatch between number of source lists and number of chips (readouts)");
+		return false;
+	    }
+	    psArray *sources = sourceArrays->data[nSrc];
+	    psMetadataAddPtr (chip->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES.TEXT", PS_DATA_ARRAY | PS_META_REPLACE, "loaded source from textfile", sources);
+	    nSrc ++;
+	}
+	psFree (view);
+    }
+    psFree (sourceArrays);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMagnitudes.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMagnitudes.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMagnitudes.c	(revision 27622)
@@ -0,0 +1,328 @@
+# include "psphotInternal.h"
+
+bool psphotMagnitudes (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+	// find the currently selected readout
+	pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", i); // File of interest
+	psAssert (file, "missing file?");
+
+	pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+	psAssert (readout, "missing readout?");
+
+	pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+	psAssert (detections, "missing detections?");
+
+	psArray *sources = detections->allSources;
+	psAssert (sources, "missing sources?");
+
+	pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+	psAssert (psf, "missing psf?");
+
+	if (!psphotMagnitudesReadout (config, recipe, view, readout, sources, psf)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotMagnitudesReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources, pmPSF *psf) {
+
+    bool status = false;
+    int Nap = 0;
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source magnitudes");
+	return true;
+    }
+	
+    psTimerStart ("psphot.mags");
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // if backModel or backStdev are missing, the values of sky and/or skyErr will be set to NAN
+    pmReadout *backModel = psphotSelectBackground (config, view);
+    pmReadout *backStdev = psphotSelectBackgroundStdev (config, view);
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    pmSourceMagnitudesInit (recipe);
+
+    // the binning details are saved on the analysis metadata
+    psImageBinning *binning = NULL;
+    if (backModel) {
+        binning = psMetadataLookupPtr(&status, backModel->analysis, "PSPHOT.BACKGROUND.BINNING");
+    }
+
+    bool IGNORE_GROWTH  = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+    bool DIFF_STATS     = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_APCORR | PM_SOURCE_PHOT_WEIGHT;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+    if (DIFF_STATS)     photMode |= PM_SOURCE_PHOT_DIFFSTATS;
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_MAGNITUDES");
+
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            psArrayAdd(job->args, 1, psf);
+            psArrayAdd(job->args, 1, binning);
+            psArrayAdd(job->args, 1, backModel);
+            psArrayAdd(job->args, 1, backStdev);
+
+            PS_ARRAY_ADD_SCALAR(job->args, photMode, PS_TYPE_S32);
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,  PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, markVal,  PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, 0,        PS_TYPE_S32); // this is used as a return value for nAp
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                psFree (job);
+                return false;
+            }
+            psFree(job);
+
+# if (0)
+                int nap = 0;
+                if (!psphotMagnitudes_Unthreaded (&nap, cells->data[j], psf, binning, backModel, backStdev, photMode, maskVal)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                    return false;
+                }
+                Nap += nap;
+# endif
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            } else {
+                psScalar *scalar = job->args->data[8];
+                Nap += scalar->data.S32;
+            }
+            psFree(job);
+        }
+    }
+
+    psFree (cellGroups);
+
+    psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "measure magnitudes : %f sec for %ld objects (%d with apertures)\n", psTimerMark ("psphot.mags"), sources->n, Nap);
+    return true;
+}
+
+bool psphotMagnitudes_Threaded (psThreadJob *job) {
+
+    bool status;
+    int Nap = 0;
+
+    psArray *sources                = job->args->data[0];
+    pmPSF *psf                      = job->args->data[1];
+    psImageBinning *binning         = job->args->data[2];
+    pmReadout *backModel            = job->args->data[3];
+    pmReadout *backStdev            = job->args->data[4];
+    pmSourcePhotometryMode photMode = PS_SCALAR_VALUE(job->args->data[5],S32);
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[6],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType markVal         = PS_SCALAR_VALUE(job->args->data[7],PS_TYPE_IMAGE_MASK_DATA);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+
+	// clear the mask bit and set the circular mask pixels
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+	psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", markVal);
+
+        status = pmSourceMagnitudes (source, psf, photMode, maskVal); // maskVal includes markVal
+        if (status && isfinite(source->apMag)) Nap ++;
+
+	// clear the mask bit 
+	psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+        if (backModel) {
+            psAssert (binning, "if backModel is defined, so should binning be");
+            source->sky = psImageUnbinPixel(source->peak->x, source->peak->y, backModel->image, binning);
+            if (isnan(source->sky) && false) {
+                psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "error setting pmSource.sky");
+            }
+        } else {
+            source->sky = NAN;
+        }
+
+        if (backStdev) {
+            psAssert (binning, "if backStdev is defined, so should binning be");
+            source->skyErr = psImageUnbinPixel(source->peak->x, source->peak->y, backStdev->image, binning);
+            if (isnan(source->skyErr) && false) {
+                psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "error setting pmSource.skyErr");
+            }
+        } else {
+            source->skyErr = NAN;
+        }
+    }
+
+    // change the value of a scalar on the array (wrap this and put it in psArray.h)
+    psScalar *scalar = job->args->data[8];
+    scalar->data.S32 = Nap;
+
+    return true;
+}
+
+bool psphotPSFWeights(pmConfig *config, pmReadout *readout, const pmFPAview *view, psArray *sources) {
+
+    bool status = false;
+
+    psTimerStart ("psphot.mags");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+    nThreads = 0; // XXX until testing is complete, do not thread this function
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_PSF_WEIGHTS");
+
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal,  PS_TYPE_IMAGE_MASK);
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+                psFree (job);
+                return false;
+            }
+            psFree(job);
+
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            }
+            psFree(job);
+        }
+    }
+
+    psFree (cellGroups);
+
+    psLogMsg ("psphot", PS_LOG_DETAIL, "measure psf weights : %f sec for %ld objects\n", psTimerMark ("psphot.mags"), sources->n);
+    return true;
+}
+
+bool psphotPSFWeights_Threaded (psThreadJob *job) {
+
+    bool status;
+    bool isPSF;
+
+    psArray *sources                = job->args->data[0];
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[1],PS_TYPE_IMAGE_MASK_DATA);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+        // we must have a valid model
+        pmModel *model = pmSourceGetModel (&isPSF, source);
+        if (model == NULL) {
+          psTrace ("psphot", 3, "fail mag : no valid model");
+          source->pixWeight = NAN;
+          continue;
+        }
+
+        status = pmSourcePixelWeight (&source->pixWeight, model, source->maskObj, maskVal);
+        if (!status) {
+          psTrace ("psphot", 3, "fail to measure pixel weight");
+          source->pixWeight = NAN;
+          continue;
+        }
+
+    }
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeFluxScale.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeFluxScale.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeFluxScale.c	(revision 27622)
@@ -0,0 +1,82 @@
+# include "psphotInternal.h"
+
+bool psphotMakeFluxScale (psImage *image, psMetadata *recipe, pmPSF *psf) {
+
+    bool status = false;
+
+    psTimerStart ("psphot.fluxscale");
+
+    int Nx = psMetadataLookupS32(&status, recipe, "PSF.FLUXSCALE.NX");
+    int Ny = psMetadataLookupS32(&status, recipe, "PSF.FLUXSCALE.NY");
+
+    // stats structure for use by ApTrend : XXX make parameters user setable
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+
+    pmTrend2D *trend = pmTrend2DAlloc (PM_TREND_MAP, image, Nx, Ny, stats);
+
+    psVector *xPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+    psVector *yPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+    psVector *fPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+
+    int Npts = 0;
+    bool success = true;                // Function succeeded?
+
+    // generate a set of test normalized PSF fluxes filling the grid
+    for (int ix = 0; ix < Nx; ix++) {
+        for (int iy = 0; iy < Ny; iy++) {
+
+            float x = psImageBinningGetFineX (trend->map->binning, ix + 0.5);
+            float y = psImageBinningGetFineY (trend->map->binning, iy + 0.5);
+
+	    float fitSum = 0;
+
+            // create normalized model object at xc,yc
+            pmModel *model = pmModelFromPSFforXY (psf, x, y, 1.0);
+            if (!model) {
+                psTrace ("psphot", 3, "Unable to generate model for grid point %d,%d", ix, iy);
+		fitSum = NAN;
+            } else {
+		// measure the fitMag for this model
+		fitSum = model->modelFlux (model->params);
+	    }
+	    if (fitSum < 1.e-6) continue;
+	    if (!isfinite(fitSum)) continue;
+
+            xPts->data.F32[Npts] = x;
+            yPts->data.F32[Npts] = y;
+            fPts->data.F32[Npts] = fitSum;
+            Npts ++;
+            assert (Npts <= xPts->nalloc);
+            psFree (model);
+        }
+    }
+    xPts->n = Npts;
+    yPts->n = Npts;
+    fPts->n = Npts;
+
+    if (!pmTrend2DFit (trend, NULL, 0xff, xPts, yPts, fPts, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to fit trend");
+        success = false;
+        goto DONE;
+    }
+    if (trend->mode == PM_TREND_MAP) {
+	// p_psImagePrint (2, trend->map->map, "FluxScale Before"); // XXX TEST:
+	psImageMapRepair (trend->map->map);
+	// p_psImagePrint (2, trend->map->map, "FluxScale After"); // XXX TEST:
+    }
+
+    // XXX do something useful to measure residual statistics
+
+    psf->FluxScale = psMemIncrRefCounter(trend);
+
+ DONE:
+    psFree(xPts);
+    psFree(yPts);
+    psFree(fPts);
+    psFree(stats);
+    psFree(trend);
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "built flux scale: %f sec\n", psTimerMark ("psphot.fluxscale"));
+
+    return success;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeGrowthCurve.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeGrowthCurve.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeGrowthCurve.c	(revision 27622)
@@ -0,0 +1,39 @@
+# include "psphotInternal.h"
+
+bool psphotMakeGrowthCurve (pmReadout *readout, psMetadata *recipe, pmPSF *psf) {
+
+    bool status;
+
+    psTimerStart ("psphot.growth");
+
+    // set limits on the aperture magnitudes
+    pmSourceMagnitudesInit (recipe);
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // measure the aperture loss as a function of radius for PSF
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    float REF_RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_REF_RADIUS");
+    float PSF_FIT_PAD   = psMetadataLookupF32 (&status, recipe, "PSF_FIT_PADDING");
+
+    psf->growth = pmGrowthCurveAlloc (PSF_FIT_PAD, 100.0, REF_RADIUS);
+
+    if (!pmGrowthCurveGenerate (readout, psf, IGNORE_GROWTH, maskVal, markVal)) {
+        psError(PSPHOT_ERR_APERTURE, false, "Fitting aperture corrections");
+        psFree(psf->growth); psf->growth = NULL;
+        return false;
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "built growth curve: %f sec\n", psTimerMark ("psphot.growth"));
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSF.c	(revision 27622)
@@ -0,0 +1,35 @@
+# include "psphotStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();                  // register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotMakePSFArguments (argc, argv);
+    assert(config);
+
+    psphotVersionPrint();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotMakePSFImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFArguments.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFArguments.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFArguments.c	(revision 27622)
@@ -0,0 +1,189 @@
+# include "psphotStandAlone.h"
+
+static void writeHelpInfo(const char* program, pmConfig* config, FILE* ofile)
+{
+  fprintf(ofile,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "%s -help\n"
+	  "\n"
+	  "%s -version\n"
+	  "\n"
+	  "where:\n"
+	  "  FileNameList is a text file containing filenames, one per line\n"
+	  "  MaskFileNameList is a text file of mask filenames, one per line\n"
+	  "  VarFileNameList is a text file of variance filenames, one per line\n"
+	  "  OutFileBaseName is the 'root name' for output files\n"
+	  "\n"
+	  "additional options:\n"
+	  "  -psf PsfFile1[,PsfFile2,...] or -psflist PsfFileNameList\n"
+	  "     specify PSF rather than letting %s estimate it\n"
+	  "  -src SrcFile1[,SrcFile2,...] or -srclist SrcFileNameList\n"
+	  "     specify additional sources for PSF generation\n"
+	  "  -chip nn[,nn,...]\n"
+	  "     select detector chips to process; default is all.\n"
+	  "     Indices correspond to zero-based offset in the FPA metadata table.\n"
+	  "  -photcode PhotoCodeName\n"
+	  "     specify photocode\n"
+	  "  -region RegionString\n"
+	  "     specify analysis region.  String is of form '[x0:x1,y0:y1]'\n"
+	  "     To use this option you must define a default in psphot.config\n"
+	  "  -visual\n"
+	  "     turns on interactive display mode\n"
+	  "  -dumpconfig CfgFileName\n"
+          "     causes config info to be dumped to the named file.\n"
+	  "  -break NOTHING|BACKMDL|PEAKS|MOMENTS|PSFMODEL|ENSEMBLE|PASS1\n"
+	  "     choose a point at which to exit processing early\n"
+	  "  -nthreads n\n"
+	  "     set number of parallel threads of execution\n"
+	  "  -F OldFileRule ReplacementFileRule\n"
+	  "     change file naming rule; e.g. '-F PSPHOT.OUTPUT PSPHOT.OUT.CMF.MEF'\n"
+	  "  -D name stringval\n"
+	  "     set a string-valued config parameter\n"
+	  "  -Di name intval\n"
+	  "     set an integer-valued config parameter\n"
+	  "  -Df name fval\n"
+	  "     set a float-valued config parameter\n"
+	  "  -Db name boolval\n"
+	  "     set a boolean-valued config parameter\n"
+	  "  -v, -vv, -vvv\n"
+	  "     set increasing levels of verbosity\n"
+	  "  -logfmt FormatString\n"
+	  "     set format string used for log messages\n"
+	  "  -trace Fac Lvl\n"
+	  "     set tracing for facility Fac to integer Lvl, e.g. '-trace err 10'\n"
+	  "  -trace-levels\n"
+	  "     print current trace levels\n",
+	  program,program,program,program,program);
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_SUCCESS);
+}
+
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments, // Command-line arguments
+                  pmConfig *config,      // Configuration
+		  int exitCode
+		  ) 
+{
+  fprintf(stderr,
+	  "Usage: one of the following\n"
+	  "%s -file fname1[,fname2,...] -mask maskfile1[,maskfile2,...]\n"
+	  "     -variance varfile1[,varfile2,...] OutFileBaseName\n"
+	  "\n"
+	  "%s -list FileNameList [-masklist MaskFileNameList] \n"
+	  "     -variancelist VarFileNameList OutFileBaseName\n"
+	  "\n"
+	  "Try '%s -help' for more options and explanation\n",
+	  program,program,program);
+    if (exitCode != PS_EXIT_SUCCESS)
+      psErrorStackPrint(stderr, "Error reading arguments\n");
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(exitCode);
+}
+
+pmConfig *psphotMakePSFArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+      psErrorStackPrint(stderr, "Can't read site configuration");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    PSARGUMENTS_INSTANTIATE_GENERICS( psphot, config, argc, argv );
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // Number of threads is handled
+    PSARGUMENTS_INSTANTIATE_THREADSARG( psphot, config, argc, argv )
+
+    // visual : interactive display mode
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+	if (argc<=N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    //
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",       "-mask",     "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "VARIANCE",   "-variance", "-variancelist");
+
+    // an input list of sources to use is allowed, but not required
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC", "-src", "-srclist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SRCTEXT", "-srctext", "-srctextlist");
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (psArgumentGet(argc, argv, "-help") ||
+	psArgumentGet(argc, argv, "-h"))
+      writeHelpInfo(argv[0], config, stdout);
+      
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+	usage(argv[0], config->arguments, config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotMakePSFArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFImageLoop.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFImageLoop.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFImageLoop.c	(revision 27622)
@@ -0,0 +1,129 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotMakePSFImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *load = psMetadataLookupPtr (&status, config->files, "PSPHOT.LOAD");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+        return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+        psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmHDU *lastHDU = NULL;              // Last HDU updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // for psphot, we force data to be read at the chip level
+    while ((chip = pmFPAviewNextChip (view, load->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (! chip->process || ! chip->file_exists) { continue; }
+
+        // load just the input image data (image, mask, weight)
+        pmFPAfileActivate (config->files, false, NULL);
+        pmFPAfileActivate (config->files, true, "PSPHOT.LOAD");
+        pmFPAfileActivate (config->files, true, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, true, "PSPHOT.VARIANCE");
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+        // mosaic the cells of a chip into a single contiguous (trimmed) chip
+        if (!psphotMosaicChip(config, view, "PSPHOT.INPUT", "PSPHOT.LOAD")) ESCAPE ("Unable to mosaic chip.");
+
+        // try to load other supporting data (PSF, SRC, etc).
+        // do not re-load the following three files
+        pmFPAfileActivate (config->files, true, NULL);
+        pmFPAfileActivate (config->files, false, "PSPHOT.LOAD");
+        pmFPAfileActivate (config->files, false, "PSPHOT.MASK");
+        pmFPAfileActivate (config->files, false, "PSPHOT.VARIANCE");
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+        // re-activate files so they will be closed and freed below
+        pmFPAfileActivate (config->files, true, NULL);
+
+        // there is now only a single chip (multiple readouts?). loop over it and process
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                psLogMsg ("psphot", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (! readout->data_exists) { continue; }
+
+                // Update the header
+		pmHDU *hdu = pmHDUGetHighest(input->fpa, chip, cell);
+		if (hdu && hdu != lastHDU) {
+		    psphotVersionHeaderFull(hdu->header);
+		    lastHDU = hdu;
+                }
+
+		// if an external mask is supplied, ensure that NAN pixels are also masked
+		if (readout->mask) {
+		    psImageMaskType maskSat = pmConfigMaskGet("SAT", config); // Mask value for saturated pixels
+		    if (!pmReadoutMaskNonfinite(readout, maskSat)) {
+			psError(psErrorCodeLast(), false, "Unable to mask non-finite pixels.");
+			psFree(view);
+			return false;
+		    }
+		}
+
+                // run the actual photometry analysis on this chip/cell/readout
+                if (!psphotMakePSFReadout (config, view)) {
+                    psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+                    psFree (view);
+                    return false;
+                }
+            }
+
+            status = true;
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL.STDEV");
+            status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+            if (!status) {
+                psError(PSPHOT_ERR_PROG, false, "trouble dropping internal files");
+                psFree (view);
+                return false;
+            }
+        }
+
+        // save output which is saved at the chip level
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed output for Chip in psphot.");
+    }
+    // save output which is saved at the fpa level
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed ouput for FPA in psphot.");
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
+// PSPHOT.MASK
+// PSPHOT.VARIANCE
+//
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFReadout.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFReadout.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakePSFReadout.c	(revision 27622)
@@ -0,0 +1,76 @@
+# include "psphotInternal.h"
+
+bool psphotMakePSFReadout(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
+    // for psphot envisions threading within psphotReadout, not multiple threads calling
+    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
+    // jointly by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX);
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (config, view)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    PS_ASSERT_PTR_NON_NULL (breakPt, false);
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view);
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, view);
+    }
+
+    psphotLoadExtSources (config, view);
+
+    // If sources have been supplied, then these should be used to measure the PSF include
+    // externally-supplied sources; if not, we need to generate a set of possible PSF sources.
+    // This function updates the SN entries for the loaded sources or generates a set of
+    // detections from the image, if no external ones have been supplied.  Sources loaded from
+    // a text file have no valid SN values, but psphotChoosePSF needs to select the top
+    // PSF_MAX_NSTARS to generate the PSF.
+    if (!psphotCheckExtSources (config, view)) {
+	psLogMsg ("psphot", 3, "failure to select possible PSF sources (external or internal)");
+	return psphotReadoutCleanup (config, view);
+    }
+
+    // Use bright stellar objects to measure PSF. If we do not have enough stars to generate
+    // the PSF, build one from the SEEING guess and model class
+    if (!psphotChoosePSF (config, view)) {
+	psLogMsg ("psphot", 3, "failure to construct a psf model");
+	return psphotReadoutCleanup (config, view);
+    }
+
+    // measure aperture photometry corrections
+# if 0
+    if (!psphotApResid (config, view)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, view);
+    }
+# endif
+
+    return psphotReadoutCleanup (config, view);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeResiduals.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeResiduals.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMakeResiduals.c	(revision 27622)
@@ -0,0 +1,343 @@
+# include "psphotInternal.h"
+
+# define RESIDUAL_SOFTENING 0.005 
+
+bool psphotMakeResiduals (psArray *sources, psMetadata *recipe, pmPSF *psf, psImageMaskType maskVal) {
+
+    bool status, isPSF;
+    double flux, dflux;
+    psImageMaskType mflux;
+
+    psTimerStart ("psphot.residuals");
+
+    if (!psMetadataLookupBool(&status, recipe, "PSF.RESIDUALS")) return true;
+
+    int SPATIAL_ORDER = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.SPATIAL_ORDER");
+    PS_ASSERT (status, false);
+    if (SPATIAL_ORDER != 0 && SPATIAL_ORDER != 1) {
+        psError(PSPHOT_ERR_CONFIG, true, "PSF.RESIDUALS.SPATIAL_ORDER must be 0 or 1 (not %d)",
+                SPATIAL_ORDER);
+        return false;
+    }
+
+    int xBin = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.XBIN");
+    PS_ASSERT (status, false);
+    PS_ASSERT (xBin != 0, false);
+
+    int yBin = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.YBIN");
+    PS_ASSERT (status, false);
+    PS_ASSERT (yBin != 0, false);
+
+    float nSigma = psMetadataLookupF32(&status, recipe, "PSF.RESIDUALS.NSIGMA");
+    PS_ASSERT (status, false);
+
+    float pixelSN = psMetadataLookupF32(&status, recipe, "PSF.RESIDUALS.PIX.SN");
+    PS_ASSERT (status, false);
+
+    float radiusMax = psMetadataLookupF32(&status, recipe, "PSF.RESIDUALS.RADIUS");
+    PS_ASSERT (status, false);
+
+    char *modeString = psMetadataLookupStr(&status, recipe, "PSF.RESIDUALS.INTERPOLATION");
+    PS_ASSERT (status, false);
+
+    psImageInterpolateMode mode = psImageInterpolateModeFromString (modeString);
+    if (mode == PS_INTERPOLATE_NONE) {
+        psError(PSPHOT_ERR_CONFIG, false, "invalid interpolation in psphot.config");
+        return false;
+    }
+
+    char *statString = psMetadataLookupStr(&status, recipe, "PSF.RESIDUALS.STATISTIC");
+    PS_ASSERT (status, false);
+
+    psStatsOptions statOption = psStatsOptionFromString (statString);
+    if (!statOption) {
+        psError(PSPHOT_ERR_CONFIG, false, "invalid residual statistic in psphot.config");
+        return false;
+    }
+
+    // user parameters:
+    // size of aperture (determine from source images?)
+    // binning factor
+
+    // select the subset of sources which are the PSFSTARs
+    // for each input source:
+    // - construct a residual image, renormalized
+    // - construct a renormalized variance image
+    // - construct a new mask image
+
+    // construct the output residual table (Nx*DX,Ny*DY)
+    // for each output pixel:
+    // - construct a histogram of the values & variances (interpolate to the common pixel coordinate)
+    // - measure the robust median & sigma
+    // - reject (mask) input pixels which are outliers
+    // - re-measure the robust median & sigma
+    // - set output pixel, variance, and mask
+
+    // these mask values do not correspond to the recipe values: they
+    // are not propagated to images: they just need to fit in an 8-bit
+    // value.  they are supplied to psImageInterpolate, which takes a
+    // psImageMaskType; the mask portion of the result from
+    // psImageInterpolate is supplied to fmasks, which is then used by
+    // psVectorStats
+
+    const psImageMaskType badMask     = 0x01;   // mask bits
+    const psImageMaskType poorMask    = 0x02;   // from psImageInterpolate
+    const psImageMaskType clippedMask = 0x04;   // mask bit set for clipped values
+    const psVectorMaskType fmaskVal = badMask | poorMask | clippedMask;
+
+    // determine the maximum image size from the input sources
+    int xSize = 0;
+    int ySize = 0;
+
+    psVector *xC = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *yC = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    // build (DATA - MODEL) [an image] for each psf star
+    psArray *input = psArrayAllocEmpty (100);
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // which model to use?
+        pmModel *model = pmSourceGetModel (&isPSF, source);
+        if (model == NULL) continue;  // model must be defined
+
+        psImage *image  = psImageCopy (NULL, source->pixels,   PS_TYPE_F32);
+        psImage *variance = psImageCopy (NULL, source->variance,   PS_TYPE_F32);
+        psImage *mask   = psImageCopy (NULL, source->maskView, PS_TYPE_IMAGE_MASK);
+        pmModelSub (image, mask, model, PM_MODEL_OP_FUNC, maskVal);
+
+        // re-normalize image and variance
+        float Io = model->params->data.F32[PM_PAR_I0];
+        psBinaryOp (image, image, "/", psScalarAlloc(Io, PS_TYPE_F32));
+        psBinaryOp (variance, variance, "/", psScalarAlloc(Io*Io, PS_TYPE_F32));
+
+        // we interpolate the image and variance - include the mask or not?
+        // XXX why not the mask?
+        // psImageInterpolation *interp = psImageInterpolationAlloc(mode, image, variance, mask, maskVal, 0.0, 0.0, badMask, poorMask, 0.0, 0);
+        psImageInterpolation *interp = psImageInterpolationAlloc(mode, image, variance, NULL, 0xff, 0.0, 0.0, badMask, poorMask, 0.0, 0);
+        psArrayAdd (input, 100, interp);
+
+        // save the X,Y position for future reference
+        xC->data.F32[xC->n] = model->params->data.F32[PM_PAR_XPOS];
+        yC->data.F32[yC->n] = model->params->data.F32[PM_PAR_YPOS];
+        psVectorExtend (xC, 100, 1);
+        psVectorExtend (yC, 100, 1);
+
+        xSize = PS_MAX (xSize, image->numCols);
+        ySize = PS_MAX (ySize, image->numRows);
+
+        // free up the excess references
+        psFree (mask);
+        psFree (image);
+        psFree (variance);
+        psFree (interp);
+    }
+    pmResiduals *resid = pmResidualsAlloc (xSize, ySize, xBin, yBin);
+    psImageInit (resid->mask, 0);
+
+    // x(resid) = (x(image) - Xo)*xBin + xCenter
+
+    psVector *fluxes  = psVectorAlloc (input->n, PS_TYPE_F32);
+    psVector *dfluxes = psVectorAlloc (input->n, PS_TYPE_F32);
+    psVector *fmasks  = psVectorAlloc (input->n, PS_TYPE_VECTOR_MASK);
+
+    // statistic to use to determine baseline for clipping
+    psStats *fluxClip     = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psStats *fluxClipDef  = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    // statistic to use to determine output flux
+    // XXX make API to convert statOption for MEAN/MEDIAN in to corresponding STDEV?
+    psStats *fluxStats    = psStatsAlloc (statOption | PS_STAT_SAMPLE_STDEV);
+    psStats *fluxStatsDef = psStatsAlloc (statOption | PS_STAT_SAMPLE_STDEV);
+
+    // Use psF64 to minimize overflow problems?
+    psImage *A = psImageAlloc(3, 3, PS_TYPE_F64); // Least-squares matrix
+    psVector *B = psVectorAlloc(3, PS_TYPE_F64); // Least-squares vector
+
+    // Solve MODEL = R + x R_x + y R_y in pixel-by-pixel a least-squares sense
+    // (If SPATIAL_ORDER == 0, just solve MODEL = R)
+    for (int oy = 0; resid != NULL && oy < resid->Ro->numRows; oy++) {
+        for (int ox = 0; ox < resid->Ro->numCols; ox++) {
+
+            int nGoodPixel = 0;              // pixel is off the image
+
+            // build the vector of data values for this output pixel
+            for (int i = 0; i < input->n; i++) {
+
+                psImageInterpolation *interp = input->data[i];
+
+                // fractional image position
+                float ix = (ox + 0.5 - resid->xCenter) / (float) xBin + xC->data.F32[i] - interp->image->col0;
+                float iy = (oy + 0.5 - resid->yCenter) / (float) yBin + yC->data.F32[i] - interp->image->row0;
+
+                mflux = 0;
+                bool offImage = false;
+                if (psImageInterpolate (&flux, &dflux, &mflux, ix, iy, interp) == PS_INTERPOLATE_STATUS_OFF) {
+                    // This pixel is off the image
+                    offImage = true;
+                    fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i] = badMask;
+                } else {
+                  fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i] = mflux; // XXX is mflux IMAGE or VECTOR type?
+                }
+                fluxes->data.F32[i] = flux;
+                dfluxes->data.F32[i] = hypot(dflux, RESIDUAL_SOFTENING);
+                if (isnan(flux)) {
+                    fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i] = badMask;
+                }
+                if (isnan(dflux)) {
+                    fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i] = badMask;
+                }
+                if (fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i] == 0) {
+                    nGoodPixel ++;
+                }
+            }
+
+            // skip pixels with insufficient data
+            bool validPixel = (SPATIAL_ORDER == 0) ? (nGoodPixel > 1) : (nGoodPixel > 3);
+            if (!validPixel) {
+                resid->Ro->data.F32[oy][ox] = 0.0;
+                resid->Rx->data.F32[oy][ox] = 0.0;
+                resid->Ry->data.F32[oy][ox] = 0.0;
+                resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = badMask;
+                continue;
+            }
+
+            // measure the robust median to determine a baseline reference value
+            *fluxClip = *fluxClipDef;
+            if (!psVectorStats (fluxClip, fluxes, NULL, fmasks, fmaskVal)) {
+		psError(PSPHOT_ERR_CONFIG, false, "Error calculating residual stats");
+		return false;
+	    }
+	    if (isnan(fluxClip->robustMedian)) {
+                resid->Ro->data.F32[oy][ox] = 0.0;
+                resid->Rx->data.F32[oy][ox] = 0.0;
+                resid->Ry->data.F32[oy][ox] = 0.0;
+                resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = badMask;
+                continue;
+	    }
+
+            // mark input pixels which are more than N sigma from the median
+            int nKeep = 0;
+            for (int i = 0; i < fluxes->n; i++) {
+                float delta = fluxes->data.F32[i] - fluxClip->robustMedian;
+                float sigma = sqrt (dfluxes->data.F32[i]);
+                float swing = fabs(delta) / sigma;
+
+                // mask pixels which are out of range
+                if (swing > nSigma) {
+                    fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i] = clippedMask;
+                }
+                if (!fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i]) nKeep++;
+            }
+
+            if (SPATIAL_ORDER == 0) {
+                // measure the desired statistic on the unclipped pixels
+                *fluxStats = *fluxStatsDef;
+                if (!psVectorStats (fluxStats, fluxes, NULL, fmasks, fmaskVal)) {
+		    psError(PSPHOT_ERR_CONFIG, false, "Error calculating residual stats");
+		    return false;
+		}
+
+		float radius = hypot((ox - 0.5*resid->Ro->numCols), (oy - 0.5*resid->Ro->numRows));
+		if (radius > radiusMax) {
+                  resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = 1;
+		  continue;
+                }
+
+                resid->Ro->data.F32[oy][ox] = psStatsGetValue(fluxStats, statOption);
+                resid->Rx->data.F32[oy][ox] = resid->Ry->data.F32[oy][ox] = 0.0;
+
+		if (isnan(resid->Ro->data.F32[oy][ox])) {
+		    resid->Ro->data.F32[oy][ox] = 0.0;
+		    resid->Rx->data.F32[oy][ox] = 0.0;
+		    resid->Ry->data.F32[oy][ox] = 0.0;
+		    resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = badMask;
+		    continue;
+		}
+
+                if (fabs(resid->Ro->data.F32[oy][ox]) < pixelSN*fluxStats->sampleStdev/sqrt(nKeep)) {
+                  resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = 1;
+                }
+            } else {
+                assert (SPATIAL_ORDER == 1);
+
+		float radius = hypot((ox - 0.5*resid->Ro->numCols), (oy - 0.5*resid->Ro->numRows));
+		if (radius > radiusMax) {
+                  resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = 1;
+		  continue;
+                }
+
+                psImageInit(A, 0.0);
+                psVectorInit(B, 0.0);
+                for (int i = 0; i < fluxes->n; i++) {
+                    if (fmasks->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
+                    B->data.F64[0] += fluxes->data.F32[i]/dfluxes->data.F32[i];
+                    B->data.F64[1] += fluxes->data.F32[i]*xC->data.F32[i]/dfluxes->data.F32[i];
+                    B->data.F64[2] += fluxes->data.F32[i]*yC->data.F32[i]/dfluxes->data.F32[i];
+
+                    A->data.F64[0][0] += 1.0/dfluxes->data.F32[i];
+                    A->data.F64[1][0] += xC->data.F32[i]/dfluxes->data.F32[i];
+                    A->data.F64[2][0] += yC->data.F32[i]/dfluxes->data.F32[i];
+
+                    A->data.F64[1][1] += PS_SQR(xC->data.F32[i])/dfluxes->data.F32[i];
+                    A->data.F64[2][2] += PS_SQR(yC->data.F32[i])/dfluxes->data.F32[i];
+                    A->data.F64[1][2] += xC->data.F32[i]*yC->data.F32[i]/dfluxes->data.F32[i];
+                }
+
+                A->data.F64[0][1] = A->data.F64[1][0];
+                A->data.F64[0][2] = A->data.F64[2][0];
+                A->data.F64[2][1] = A->data.F64[1][2];
+
+                if (!psMatrixGJSolve(A, B)) {
+		    resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = 1;
+                    psWarning("Singular matrix solving for (y,x) = (%d,%d)'s residuals, masking", oy, ox);
+		    continue;
+                }
+
+                resid->Ro->data.F32[oy][ox] = B->data.F64[0];
+                resid->Rx->data.F32[oy][ox] = B->data.F64[1];
+                resid->Ry->data.F32[oy][ox] = B->data.F64[2];
+
+                float dRo = sqrt(A->data.F32[0][0]);
+
+                if (fabs(resid->Ro->data.F32[oy][ox]) < pixelSN*dRo/sqrt(nKeep)) {
+                  resid->mask->data.PM_TYPE_RESID_MASK_DATA[oy][ox] = 1;
+		  resid->Ro->data.F32[oy][ox] = 0.0;
+		  resid->Rx->data.F32[oy][ox] = 0.0;
+		  resid->Ry->data.F32[oy][ox] = 0.0;
+                }
+            }
+        }
+    }
+
+    psFree (A);
+    psFree (B);
+
+    psLogMsg ("psphot.pspsf", PS_LOG_MINUTIA, "generate residuals for %ld objects: %f sec\n", input->n, psTimerMark ("psphot.residuals"));
+
+    psFree (xC);
+    psFree (yC);
+    psFree (input);
+
+    psFree (fluxes);
+    psFree (dfluxes);
+    psFree (fmasks);
+
+    psFree (fluxStats);
+    psFree (fluxStatsDef);
+    psFree (fluxClip);
+    psFree (fluxClipDef);
+
+    if (resid != NULL && psTraceGetLevel("psphot") > 5) {
+      psphotSaveImage (NULL, resid->Ro,     "resid.ro.fits");
+      psphotSaveImage (NULL, resid->Rx,     "resid.rx.fits");
+      psphotSaveImage (NULL, resid->Ry,     "resid.ry.fits");
+      psphotSaveImage (NULL, resid->variance, "resid.wt.fits");
+      psphotSaveImage (NULL, resid->mask,   "resid.mk.fits");
+    }
+
+    psf->residuals = resid;
+    return (resid != NULL) ? true : false;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMaskReadout.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMaskReadout.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMaskReadout.c	(revision 27622)
@@ -0,0 +1,107 @@
+# include "psphotInternal.h"
+
+bool psphotSetMaskAndVariance (pmConfig *config, const pmFPAview *view) {
+
+    bool status = false;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+
+	// Generate the mask and weight images, including the user-defined analysis region of interest
+	if (!psphotSetMaskAndVarianceReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to generate mask for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// generate mask and variance if not defined, additional mask for restricted subregion
+bool psphotSetMaskAndVarianceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status;
+
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", index); // File of interest
+    psAssert (file, "missing file?");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAviewThisReadout (view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // save maskSat and maskBad on the psphot recipe (mostly for psphotRoughClass)
+    psImageMaskType maskSat  = pmConfigMaskGet("SAT", config); // Mask value for saturated pixels
+    psMetadataAddImageMask (recipe, PS_LIST_TAIL, "MASK.SAT", PS_META_REPLACE, "user-defined mask", maskSat);
+
+    psImageMaskType maskBad  = pmConfigMaskGet("LOW", config); // Mask value for low pixels
+    if (!maskBad) {
+        // for backward compatability look up old name
+        maskBad  = pmConfigMaskGet("BAD", config);
+    }
+    psMetadataAddImageMask (recipe, PS_LIST_TAIL, "MASK.BAD", PS_META_REPLACE, "user-defined mask", maskBad);
+
+    // generate mask & variance images if they don't already exit
+    if (!readout->mask) {
+        if (!pmReadoutGenerateMask(readout, maskSat, maskBad)) {
+            psError (PSPHOT_ERR_CONFIG, false, "trouble creating mask");
+            return false;
+        }
+    }
+    if (!readout->variance) {
+        if (!pmReadoutGenerateVariance(readout, NULL, true)) {
+            psError (PSPHOT_ERR_CONFIG, false, "trouble creating variance");
+            return false;
+        }
+    }
+
+    bool softenVariance = psMetadataLookupBool (&status, recipe, "SOFTEN.VARIANCE");
+    float softenFraction = psMetadataLookupF32 (&status, recipe, "SOFTEN.VARIANCE.FRACTION");
+
+    // make this an option via the recipe
+    if (softenVariance) {
+      psImage *im = readout->image;
+      psImage *wt = readout->variance;
+      for (int j = 0; j < im->numRows; j++) {
+        for (int i = 0; i < im->numCols; i++) {
+	    if (!isfinite(im->data.F32[j][i])) continue;
+	    if (!isfinite(wt->data.F32[j][i])) continue;
+	    float sysError = softenFraction * im->data.F32[j][i];
+	    wt->data.F32[j][i] += PS_SQR(sysError);
+        }
+      }
+    }
+
+    // mask the excluded outer pixels
+    // these coordinates refer to the parent image
+    // these bounds will saturate on the subimage
+    // negative upper bounds will subtract from the *subimage*
+    float XMIN  = psMetadataLookupF32 (&status, recipe, "XMIN");
+    float XMAX  = psMetadataLookupF32 (&status, recipe, "XMAX");
+    float YMIN  = psMetadataLookupF32 (&status, recipe, "YMIN");
+    float YMAX  = psMetadataLookupF32 (&status, recipe, "YMAX");
+    psRegion valid = psRegionSet (XMIN, XMAX, YMIN, YMAX);
+
+    // restrict the supplied region above to the valid area on the image
+    psRegion keep = psRegionForImage (readout->image, valid);
+
+    // psImageKeepRegion assumes the region refers to the parent coordinates
+    psImageKeepRegion (readout->mask, keep, "OR", maskBad);
+
+    // test output of files at this stage
+    if (psTraceGetLevel("psphot") >= 5) {
+        psphotSaveImage (NULL, readout->image,  "image.fits");
+        psphotSaveImage (NULL, readout->mask,   "mask.fits");
+        psphotSaveImage (NULL, readout->variance, "variance.fits");
+    }
+
+    // display the image, weight, mask (ch 1,2,3)
+    psphotVisualShowImage (readout);
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMergeSources.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMergeSources.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMergeSources.c	(revision 27622)
@@ -0,0 +1,397 @@
+# include "psphotInternal.h"
+
+#define PSF_SOURCE_MASK (PM_SOURCE_MODE_FAIL | PM_SOURCE_MODE_SATSTAR | PM_SOURCE_MODE_BLEND | \
+                         PM_SOURCE_MODE_BADPSF | PM_SOURCE_MODE_DEFECT | PM_SOURCE_MODE_SATURATED | \
+                         PM_SOURCE_MODE_CR_LIMIT | PM_SOURCE_MODE_EXT_LIMIT) // Mask to apply for PSF sources
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotMergeSources (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotMergeSourcesReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to merge sources for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// add newly selected sources to the existing list of sources
+bool psphotMergeSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *newSources = detections->newSources;
+    psAssert (newSources, "missing sources?");
+
+    // XXX TEST:
+    if (detections->allSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->allSources);
+    }
+    if (detections->newSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->newSources);
+    }
+
+    if (!detections->allSources) {
+        detections->allSources = psArrayAllocEmpty(newSources->n);
+    }
+    psArray *allSources = detections->allSources;
+
+    for (int i = 0; i < newSources->n; i++) {
+        pmSource *source = newSources->data[i];
+        psArrayAdd (allSources, 100, source);
+    }
+
+    psFree (detections->newSources);
+    detections->newSources = NULL;
+
+    return true;
+}
+
+// Merge the externally supplied sources with the existing sources.  Mark them as having mode
+// PM_SOURCE_MODE_EXTERNAL.
+
+// XXX This function needs to be updated to loop over set of input files.  At the moment, we
+// only expect a single entry for PSPHOT.INPUT.CMF and PSPHOT.SOURCES.TEXT, so we can only
+// associate input sources with a single entry for PSPHOT.INPUT
+bool psphotLoadExtSources (pmConfig *config, const pmFPAview *view) {
+
+    bool status;
+    pmDetections *extCMF = NULL;
+    psArray *extSourcesTXT = NULL;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (!detections) {
+	detections = pmDetectionsAlloc();
+	detections->newSources = psArrayAllocEmpty (100);
+	// save detections on the readout->analysis
+	if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot detections", detections)) {
+	    psError (PSPHOT_ERR_CONFIG, false, "problem saving detections on readout");
+	    return false;
+	}
+    } else {
+	psMemIncrRefCounter(detections); // so we can free the detections below
+    }
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    // load data from input CMF file:
+    {
+        pmReadout *readoutCMF = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CMF");
+        if (!readoutCMF) goto loadTXT;
+
+        extCMF = psMetadataLookupPtr (NULL, readoutCMF->analysis, "PSPHOT.DETECTIONS");
+        if (extCMF) {
+            for (int i = 0; i < extCMF->allSources->n; i++) {
+                pmSource *source = extCMF->allSources->data[i];
+                source->mode |= PM_SOURCE_MODE_EXTERNAL;
+
+                // the supplied peak flux needs to be re-normalized
+                source->peak->flux = 1.0;
+                source->peak->value = 1.0;
+
+                // drop the loaded source modelPSF
+                psFree (source->modelPSF);
+                source->modelPSF = NULL;
+
+                psArrayAdd (detections->newSources, 100, source);
+            }
+        }
+    }
+
+loadTXT:
+
+    // load data from input TXT file:
+    {
+        pmChip *chipTXT = pmFPAfileThisChip (config->files, view, "PSPHOT.INPUT");
+        if (!chipTXT) goto finish;
+
+        extSourcesTXT = psMetadataLookupPtr (NULL, chipTXT->analysis, "PSPHOT.SOURCES.TEXT");
+        if (extSourcesTXT) {
+            for (int i = 0; i < extSourcesTXT->n; i++) {
+                pmSource *source = extSourcesTXT->data[i];
+                source->mode |= PM_SOURCE_MODE_EXTERNAL;
+
+                // drop the loaded source modelPSF
+                psFree (source->modelPSF);
+                source->modelPSF = NULL;
+
+                psArrayAdd (detections->newSources, 100, source);
+            }
+        }
+    }
+
+finish:
+
+    psFree (detections);
+
+    if (!extCMF && !extSourcesTXT) {
+        psLogMsg ("psphot", 3, "no external sources for this readout");
+        return true;
+    }
+
+    int nCMF = extCMF        ? extCMF->allSources->n        : 0;
+    int nTXT = extSourcesTXT ? extSourcesTXT->n : 0;
+
+    psLogMsg ("psphot", 3, "%d external sources (%d cmf, %d text) merged to yield %ld total sources",
+              nCMF + nTXT, nCMF, nTXT, sources->n);
+    return true;
+}
+
+// extract the input sources corresponding to this readout
+// XXX this function needs to be updated to work with the new context of pshot inputs
+psArray *psphotLoadPSFSources (pmConfig *config, const pmFPAview *view) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CMF");
+    if (!readout) {
+        psLogMsg ("psphot", 3, "readout not found");
+        return NULL;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (!detections) {
+        psLogMsg ("psphot", 3, "no psf sources for this readout");
+        return NULL;
+    }
+
+    psArray *sources = detections->allSources;
+    if (!sources) {
+        psLogMsg ("psphot", 3, "no psf sources for this readout");
+        return NULL;
+    }
+
+    return sources;
+}
+
+// this function is used to fix sources which were loaded externally, but have passed from
+// psphotDetectionsFromSources to psphotSourceStats and are now stored on
+// detections->newSources.
+bool psphotRepairLoadedSources (pmConfig *config, const pmFPAview *view) {
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.DETECTIONS");
+    if (!detections) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing detections");
+        return false;
+    }
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    // peak flux is wrong : set based on previous image
+    // use the peak measured in the moments analysis:
+    for (int i = 0; i < sources->n; i++) {
+      pmSource *source = sources->data[i];
+      source->peak->flux = source->moments->Peak;
+    }
+
+    return true;
+}
+
+// generate the detection structure for the supplied array of sources
+// XXX this currently assumes there is a single input file
+bool psphotDetectionsFromSources (pmConfig *config, const pmFPAview *view, psArray *sources) {
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = pmDetectionsAlloc();
+
+    detections->peaks = psArrayAllocEmpty(100);
+
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+    float snMin = psMetadataLookupF32(NULL, recipe, "MOMENTS_SN_MIN");
+    if (!isfinite(snMin)) {
+        return false;
+    }
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel *model = source->modelPSF;
+
+        if (source->mode & PSF_SOURCE_MASK || !isfinite(source->psfMag)) {
+            continue;
+        }
+
+        // use the existing peak information, otherwise generate a new peak
+        if (source->peak) {
+            source->peak->assigned = false; // So the moments will be measured
+            psArrayAdd (detections->peaks, 100, source->peak);
+            continue;
+        }
+
+        float flux = powf(10.0, -0.4 * source->psfMag);
+        float xpos = model->params->data.F32[PM_PAR_XPOS];
+        float ypos = model->params->data.F32[PM_PAR_YPOS];
+
+        pmPeak *peak = pmPeakAlloc(xpos, ypos, 1.0, PM_PEAK_LONE);
+        peak->xf = xpos;
+        peak->yf = ypos;
+        peak->flux = flux; // this are being set wrong, but does it matter?
+
+        if (isfinite (source->errMag) && (source->errMag > 0.0)) {
+          peak->SN = 1.0 / source->errMag;
+        } else {
+          peak->SN = 0.0;
+        }
+
+        psArrayAdd (detections->peaks, 100, peak);
+        psFree (peak);
+    }
+
+    psLogMsg ("psphot", 3, "%ld PSF sources loaded", detections->peaks->n);
+
+    // save detections on the readout->analysis
+    if (!psMetadataAddPtr (readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_META_REPLACE | PS_DATA_UNKNOWN, "psphot detectinos", detections)) {
+        psError (PSPHOT_ERR_CONFIG, false, "problem saving detections on readout");
+        return false;
+    }
+    psFree (detections);
+    return true;
+}
+
+// generate the detection structure for the supplied array of sources
+bool psphotSetSourceParams (pmConfig *config, psArray *sources, pmPSF *psf) {
+
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel *model = source->modelPSF;
+
+        if (source->mode & PSF_SOURCE_MASK || !isfinite(source->psfMag)) {
+            continue;
+        }
+
+        float flux = powf(10.0, -0.4 * source->psfMag);
+        float xpos = model->params->data.F32[PM_PAR_XPOS];
+        float ypos = model->params->data.F32[PM_PAR_YPOS];
+
+        pmPeak *peak = pmPeakAlloc(xpos, ypos, 1.0, PM_PEAK_LONE);
+        peak->xf = xpos;
+        peak->yf = ypos;
+        peak->flux = flux; // this are being set wrong, but does it matter?
+
+        if (isfinite (source->errMag) && (source->errMag > 0.0)) {
+          peak->SN = 1.0 / source->errMag;
+        } else {
+          peak->SN = 0.0;
+        }
+
+        source->peak = peak;
+    }
+
+    psLogMsg ("psphot", 3, "%ld PSF sources loaded", sources->n);
+
+    return true;
+}
+
+bool psphotCheckExtSources (pmConfig *config, const pmFPAview *view) {
+
+    bool status;
+
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PSPHOT.INPUT", 0); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    // XXX allSources of newSources?
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (sources->n) {
+        // the user wants to make the psf from these stars; define them as psf stars:
+        for (int i = 0; i < sources->n; i++) {
+            pmSource *source = sources->data[i];
+            source->mode |= PM_SOURCE_MODE_PSFSTAR;
+        }
+        // force psphotChoosePSF to use all loaded sources
+        psMetadataAddS32 (recipe, PS_LIST_TAIL, "PSF_MAX_NSTARS", PS_META_REPLACE, "max number of sources for PSF model", sources->n);
+
+        // measure stats of externally specified sources
+        if (!psphotSourceStatsUpdate (sources, config, readout)) {
+            psError(PSPHOT_ERR_CONFIG, false, "failure to measure stats of existing sources");
+            return false;
+        }
+    } else {
+
+        // find the detections (by peak and/or footprint) in the image.
+        if (!psphotFindDetections (config, view, true)) {
+            psError(PSPHOT_ERR_CONFIG, false, "unable to find detections in this image");
+            return psphotReadoutCleanup (config, view);
+        }
+
+        // construct sources and measure basic stats
+        psphotSourceStats (config, view, true);
+
+        // find blended neighbors of very saturated stars
+        psphotDeblendSatstars (config, view);
+
+        // mark blended peaks PS_SOURCE_BLEND
+        if (!psphotBasicDeblend (config, view)) {
+            psLogMsg ("psphot", 3, "failed on deblend analysis");
+            return psphotReadoutCleanup (config, view);
+        }
+
+        // classify sources based on moments, brightness
+        if (!psphotRoughClass (config, view)) {
+            psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+            return psphotReadoutCleanup (config, view);
+        }
+    }
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelBackground.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelBackground.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelBackground.c	(revision 27622)
@@ -0,0 +1,401 @@
+# include "psphotInternal.h"
+static int npass = 0;
+static char *defaultStatsName = "FITTED_MEAN";
+
+// Determine the binning characteristics for the background model
+// Sets the ruff image size and skip based on recipe values for the binning
+psImageBinning *psphotBackgroundBinning(const psImage *image, // Image for which to generate a bg model
+                                         const pmConfig *config // Configuration
+                                         )
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // I have the fine image size, I know the binning factor, determine the ruff image size
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXfine = image->numCols;
+    binning->nYfine = image->numRows;
+    binning->nXbin  = psMetadataLookupS32(&status, recipe, "BACKGROUND.XBIN");
+    binning->nYbin  = psMetadataLookupS32(&status, recipe, "BACKGROUND.YBIN");
+
+    psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkip(binning, image);
+
+    return binning;
+}
+
+
+// Generate the background model
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+//
+// NOTE that the 'analysis' metedata pass in here is used to store the binning information.
+// This may be the analysis for this readout, but it may be the analysis for the pmFPAfile
+// corresponding to the model.  Other information about the background model is saved on the
+// readout->analysis
+static bool psphotModelBackgroundReadout(psImage *model,  // Model image
+                                  psImage *modelStdev, // Model stdev image
+                                  psMetadata *analysis, // Analysis metadata for outputs
+                                  pmReadout *readout, // Readout for which to generate a background model
+                                  psImageBinning *binning, // Binning parameters
+                                  const pmConfig *config // Configuration
+    )
+{
+    psTimerStart ("psphot.background");
+
+    bool status = true;
+
+    psImage *image = readout->image, *mask = readout->mask; // Image and mask for readout
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS);
+
+    // subtract this amount extra from the sky
+    float SKY_BIAS = psMetadataLookupF32 (&status, recipe, "SKY_BIAS");
+    if (!status) {
+        SKY_BIAS = 0;
+    }
+
+    // supply the sky background statistics options
+    char *statsName = psMetadataLookupStr (&status, recipe, "SKY_STAT");
+    if (statsName == NULL) {
+        statsName = defaultStatsName;
+    }
+    psStatsOptions statsOptionLocation = psStatsOptionFromString(statsName);
+    if (!(statsOptionLocation & (PS_STAT_SAMPLE_MEAN |
+                                 PS_STAT_SAMPLE_MEDIAN |
+                                 PS_STAT_ROBUST_MEDIAN |
+                                 PS_STAT_ROBUST_QUARTILE |
+                                 PS_STAT_CLIPPED_MEAN |
+                                 PS_STAT_FITTED_MEAN |
+                                 PS_STAT_FITTED_MEAN_V2 |
+                                 PS_STAT_FITTED_MEAN_V3 |
+                                 PS_STAT_FITTED_MEAN_V4))) {
+        statsOptionLocation = PS_STAT_FITTED_MEAN;
+    }
+
+    psStatsOptions statsOptionWidth = PS_STAT_NONE;
+    if (statsOptionLocation & (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN)) {
+        statsOptionWidth = PS_STAT_SAMPLE_STDEV;
+    } else if (statsOptionLocation & (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_QUARTILE)) {
+#if 1
+        statsOptionWidth = PS_STAT_ROBUST_STDEV; // not set; => NaN
+#else
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+#endif
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_CLIPPED_MEAN) {
+        statsOptionWidth = PS_STAT_CLIPPED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V2) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V2;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V3) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V3;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V4) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V4;
+    } else {
+        psAbort("Unable to estimate variance of selected statsOptionLocations 0x%x", statsOptionLocation);
+    }
+
+    const psStatsOptions statsOption = statsOptionLocation | statsOptionWidth;
+    psStats *stats = psStatsAlloc (statsOption);
+
+    // set range for old-version of sky statistic
+    if (statsOptionLocation & PS_STAT_ROBUST_QUARTILE) {
+        stats->min = 0.25;
+        stats->max = 0.75;
+    }
+
+    // set user-option for number of pixels per region
+    stats->nSubsample = psMetadataLookupF32 (&status, recipe, "IMSTATS_NPIX");
+    if (!status) {
+        stats->nSubsample = 1000;
+    }
+
+    // optionally set the binsize
+    stats->binsize = psMetadataLookupF32 (&status, recipe, "SKY_HISTOGRAM_BINS");
+    if (status) {
+        stats->options |= PS_STAT_USE_BINSIZE;
+    }
+
+    // optionally set the sigma clipping
+    stats->clipSigma = psMetadataLookupF32 (&status, recipe, "SKY_CLIP_SIGMA");
+    if (!status) {
+        if ((stats->options & PS_STAT_FITTED_MEAN) || (stats->options & PS_STAT_FITTED_MEAN_V2)) {
+            stats->clipSigma = 1.0;
+        } else {
+            stats->clipSigma = 3.0;
+        }
+    }
+
+    // stats is not initialized by psStats???  use this to save the input options
+    // XXX re-work this to use the new psStatsInit function
+    psStats *statsDefaults = psStatsAlloc (statsOption);
+    *statsDefaults = *stats;
+
+    // we save the binning structure for use in psphotMagnitudes
+    psMetadataAddPtr(analysis, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING", PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
+
+    psF32 **modelData = model->data.F32;
+    psF32 **modelStdevData = modelStdev->data.F32;
+
+    // XXXX we can thread this here by running blocks in parallel
+
+    int nFailures = 0;
+    psImageBackgroundInit();
+
+    // measure clipped median for subimages
+    psRegion ruffRegion = {0,0,0,0};
+    psRegion fineRegion = {0,0,0,0};
+    for (int iy = 0; iy < model->numRows; iy++) {
+        for (int ix = 0; ix < model->numCols; ix++) {
+
+            // convert the ruff grid cell to the equivalent fine grid cell
+            // XXX we need to watch out for row0,col0
+            ruffRegion = psRegionSet (ix, ix + 1, iy, iy + 1);
+            fineRegion = psImageBinningSetFineRegion (binning, ruffRegion);
+            fineRegion = psRegionForImage (image, fineRegion);
+
+            psImage *subset  = psImageSubset (image, fineRegion);
+            if (!subset->numCols || !subset->numRows) {
+                psFree (subset);
+                continue;
+            }
+            psImage *submask = psImageSubset (mask, fineRegion);
+
+            // reset the default values
+            *stats = *statsDefaults;
+
+            // Use the selected background statistic for the first pass
+            // If it fails, fall back on the "ROBUST_MEDIAN" version
+            // If both fail, set the pixel to NAN and (below) interpolate
+            // XXX psImageBackground will probably be renamed psImageStats
+            // XXX don't bother trying if there are no valid pixels...
+
+            psVector *sample = NULL;
+
+            // turn on stats tracing in desired cells
+            # if (0)
+            psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+            assert (plots);
+
+            int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+            assert (status);
+            int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+            assert (status);
+
+            bool gotX = (xPlot < 0) || (xPlot == ix);
+            bool gotY = (yPlot < 0) || (yPlot == iy);
+
+            if (gotX && gotY) {
+                (void) psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 6);
+                (void) psTraceSetLevel ("psLib.math.vectorRobustStats", 6);
+            } else {
+                (void) psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 0);
+                (void) psTraceSetLevel ("psLib.math.vectorRobustStats", 0);
+            }
+            # endif
+
+            if (psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                if (stats->options & PS_STAT_ROBUST_QUARTILE) {
+                    modelData[iy][ix] = stats->robustMedian;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue(stats, statsOptionLocation);
+                }
+                modelStdevData[iy][ix] = psStatsGetValue(stats, statsOptionWidth);
+
+                // supply sample to plotting routing
+                psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+            } else {
+                psStatsOptions currentOptions = stats->options;
+                stats->options = PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV;
+                if (!psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+		    if ((nFailures < 3) || (nFailures % 100 == 0)) {
+			psLogMsg ("psphot", PS_LOG_WARN, "Failed to estimate background using ROBUST_MEDIAN for "
+				  "(%dx%d, (row0,col0) = (%d,%d)",
+				  subset->numRows, subset->numCols, subset->row0, subset->col0);
+		    }
+		    nFailures ++;
+                    modelData[iy][ix] = modelStdevData[iy][ix] = NAN;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue (stats, PS_STAT_ROBUST_MEDIAN);
+                    modelStdevData[iy][ix] = psStatsGetValue(stats, PS_STAT_ROBUST_STDEV);
+
+                    // supply sample to plotting routing
+                    psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+                }
+                // drop errors caused by psImageBackground failures
+                // XXX we probably should trap and exit on serious failures
+                psErrorClear();
+                stats->options = currentOptions;
+            }
+            psFree(sample);
+            modelData[iy][ix] += SKY_BIAS;
+            psFree (subset);
+            psFree (submask);
+        }
+    }
+
+    if (nFailures) {
+	psLogMsg ("psphot", PS_LOG_WARN, "Failed to estimate background for %d of %d subimages", nFailures, (model->numRows*model->numCols));
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backraw.%02d.fits", npass);
+        psphotSaveImage (NULL, model, name);
+    }
+
+    // patch over bad regions (use average of 8 possible neighbor pixels)
+    // XXX consider testing all pixels against the 8 neighbors and replacing outliers...
+    double Count = 0;                   // number of good pixels
+    double Value = 0;                   // sum of good pixel's value
+    double ValueStdev = 0;              // sum of good pixel's standard deviations
+    for (int iy = 0; iy < model->numRows; iy++) {
+        for (int ix = 0; ix < model->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) {
+                Value += modelData[iy][ix];
+                ValueStdev += modelStdevData[iy][ix];
+                Count++;
+                continue;
+            }
+
+            double value = 0;
+            double count = 0;
+            for (int jy = iy - 1; jy <= iy + 1; jy++) {
+                if (jy <   0) continue;
+                if (jy >= model->numRows) continue;
+                for (int jx = ix - 1; jx <= ix + 1; jx++) {
+                    if (!jx && !jy) continue;
+                    if (jx   <   0) continue;
+                    if (jx   >= model->numCols) continue;
+                    value += modelData[jy][jx];
+                    count += 1.0;
+                }
+            }
+            if (count > 0) modelData[iy][ix] = value / count;
+        }
+    }
+    if (Count == 0) {
+        psError (PSPHOT_ERR_DATA, true, "failed to build background image");
+        psFree(stats);
+        psFree(statsDefaults);
+        psFree(binning);
+        psFree(rng);
+        return false;
+    }
+
+    Value /= Count;
+    ValueStdev /= Count;
+
+    // patch over remaining bad regions (use global average)
+    for (int iy = 0; iy < model->numRows; iy++) {
+        for (int ix = 0; ix < model->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) continue;
+            modelData[iy][ix] = Value;
+            modelStdevData[iy][ix] = ValueStdev;
+        }
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "built background image: %f sec\n", psTimerMark ("psphot.background"));
+
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SKY_STDEV", PS_META_REPLACE, "sky stdev", ValueStdev);
+    psLogMsg ("psphot", PS_LOG_INFO, "image sky : mean %f stdev %f", Value, ValueStdev);
+
+    // measure image and background stats and save for later output
+    psStats *statsBck = psStatsAlloc (PS_STAT_SAMPLE_MEAN |
+                                      PS_STAT_SAMPLE_STDEV |
+                                      PS_STAT_MIN |
+                                      PS_STAT_MAX);
+    psImageStats (statsBck, model, NULL, 0);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_MN", PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_SIG", PS_META_REPLACE, "sky model stdev",         statsBck->sampleStdev);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_MAX", PS_META_REPLACE, "sky model maximum value", statsBck->max);
+    psMetadataAddF32 (readout->analysis, PS_LIST_TAIL, "MSKY_MIN", PS_META_REPLACE, "sky model minimum value", statsBck->min);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "MSKY_NX", PS_META_REPLACE, "sky model size (x)",      model->numCols);
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "MSKY_NY", PS_META_REPLACE, "sky model size (y)",      model->numRows);
+    psLogMsg ("psphot", PS_LOG_INFO, "background sky : min %f mean %f max %f stdev %f",
+              statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
+
+    psFree(stats);
+    psFree(statsBck);
+    psFree(statsDefaults);
+    psFree(binning);
+    psFree(rng);
+
+    return model;
+}
+
+// generate a background model for a single readout. do not save an associated pmFPAfile for possible output
+psImage *psphotModelBackgroundReadoutNoFile(pmReadout *readout, const pmConfig *config)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, NULL);
+    PM_ASSERT_READOUT_IMAGE(readout, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psImageBinning *binning = psphotBackgroundBinning(readout->image, config); // Image binning parameters
+    psImage *model = psImageAlloc(binning->nXruff, binning->nYruff, PS_TYPE_F32); // Background model
+    psImage *modelStdev = psImageAlloc(binning->nXruff, binning->nYruff, PS_TYPE_F32); // Standard deviation
+
+    if (!psphotModelBackgroundReadout(model, modelStdev, readout->analysis, readout, binning, config)) {
+        psFree(model);
+        psFree(modelStdev);
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate background model");
+        return NULL;
+    }
+    psFree(modelStdev);
+    return model;
+}
+
+// generate a background model for readout number index; save an associated pmFPAfile for possible output
+bool psphotModelBackgroundReadoutFileIndex (pmConfig *config, const pmFPAview *view, const char *filename, int index)
+{
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmFPA *inFPA = file->fpa;
+    pmReadout *readout = pmFPAviewThisReadout(view, inFPA);
+    psAssert (readout, "missing readout?");
+
+    psImageBinning *binning = psphotBackgroundBinning(readout->image, config); // Image binning parameters
+    pmReadout *model = pmFPAGenerateReadout(config, view, "PSPHOT.BACKMDL", inFPA, binning);
+    pmReadout *modelStdev = pmFPAGenerateReadout(config, view, "PSPHOT.BACKMDL.STDEV", inFPA, binning);
+
+    if (!psphotModelBackgroundReadout(model->image, modelStdev->image, model->analysis, readout, binning, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate background model");
+        return false;
+    }
+
+    npass ++;
+    return true;
+}
+
+// XXX supply filename or keep PSPHOT.INPUT fixed?
+bool psphotModelBackground (pmConfig *config, const pmFPAview *view)
+{
+    bool status = false;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotModelBackgroundReadoutFileIndex(config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to model background for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelGroupInit.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelGroupInit.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelGroupInit.c	(revision 27622)
@@ -0,0 +1,25 @@
+# include "psphotInternal.h"
+
+// Add locally-defined models here.  As these mature, they can be moved to
+// psModule/src/objects/models
+
+# include "models/pmModel_TEST1.c"
+# include "models/pmModel_STRAIL.c"
+
+static pmModelClass userModels[] = {
+    {"PS_MODEL_TEST1", 7, pmModelFunc_TEST1,  pmModelFlux_TEST1,  pmModelRadius_TEST1,  pmModelLimits_TEST1,  pmModelGuess_TEST1, pmModelFromPSF_TEST1, pmModelParamsFromPSF_TEST1, pmModelFitStatus_TEST1, NULL},
+    {"PS_MODEL_STRAIL", 9, pmModelFunc_STRAIL,  pmModelFlux_STRAIL,  pmModelRadius_STRAIL,  pmModelLimits_STRAIL,  pmModelGuess_STRAIL, pmModelFromPSF_STRAIL, pmModelParamsFromPSF_STRAIL, pmModelFitStatus_STRAIL, NULL},
+};
+
+void psphotModelClassInit (void)
+{
+
+    // if pmModelClassInit returns false, we have already init'ed
+    if (!pmModelClassInit ()) return;
+
+    int Nmodels = sizeof (userModels) / sizeof (pmModelClass);
+    for (int i = 0; i < Nmodels; i++) {
+        pmModelClassAdd (&userModels[i]);
+    }
+    return;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelTest.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelTest.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelTest.c	(revision 27622)
@@ -0,0 +1,250 @@
+# include "psphotInternal.h"
+# define PM_SOURCE_FIT_PSF_X_EXT PM_SOURCE_FIT_PSF_AND_SKY
+
+// XXX add more test information?
+bool psphotModelTest (pmConfig *config, const pmFPAview *view, psMetadata *recipe) {
+
+    bool status;
+    int modelType = -1;
+    float obsMag, fitMag, value;
+    char name[64];
+    pmPSF *psf = NULL;
+    pmSourceFitMode fitMode;
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // run model fitting tests on a single source?
+    if (!psMetadataLookupBool (&status, recipe, "TEST_FIT")) return false;
+
+    psTimerStart ("modelTest");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, false);
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) POISSON_ERRORS = true;
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // find the various fitting parameters (try test values first)
+    float INNER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_INNER_RADIUS");
+    if (!status || !isfinite(INNER)) {
+        INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    }
+    float OUTER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_OUTER_RADIUS");
+    if (!status || !isfinite(OUTER)) {
+        OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    }
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "TEST_FIT_RADIUS");
+    if (!status || !isfinite(RADIUS)) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    }
+    float mRADIUS = psMetadataLookupF32 (&status, recipe, "TEST_MOMENTS_RADIUS");
+    if (!status || !isfinite(mRADIUS)) {
+        mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // define the source of interest
+    float xObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_X");
+    float yObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_Y");
+    if (!isfinite(xObj) || !isfinite(yObj)) psAbort ("object position is not defined");
+
+    // what fitting mode to use?
+    fitMode = PM_SOURCE_FIT_EXT;
+    char *fitModeWord = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODE");
+    if (fitModeWord && !strcasecmp (fitModeWord, "PSF")) fitMode = PM_SOURCE_FIT_PSF;
+    if (fitModeWord && !strcasecmp (fitModeWord, "CONV")) fitMode = PM_SOURCE_FIT_PSF_X_EXT;
+    if (fitModeWord && !strcasecmp (fitModeWord, "DEFAULT")) fitMode = PM_SOURCE_FIT_EXT;
+
+    // construct the source structures
+    pmSource *source = pmSourceAlloc();
+    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
+    pmSourceDefinePixels (source, readout, xObj, yObj, OUTER);
+
+    // in fitMode, psf sets the model type
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+        psf = psphotLoadPSF (config, view, recipe);
+        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
+        modelType = psf->type;
+        source->type = PM_SOURCE_TYPE_STAR;
+    }
+    if (fitMode == PM_SOURCE_FIT_EXT) {
+        // find the model: supplied by user or first in the PSF_MODEL list
+        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+        if (!status || !strcasecmp (modelName, "DEFAULT")) {
+            // get the list pointers for the PSF_MODEL entries
+
+            psList *list = NULL;
+            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+            if (mdi->type == PS_DATA_STRING) {
+                list = psListAlloc(NULL);
+                psListAdd (list, PS_LIST_HEAD, mdi);
+            } else {
+                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+                list = psMemIncrRefCounter(mdi->data.list);
+            }
+
+            // take the first list element
+            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
+            modelName = item->data.V;
+        }
+        modelType = pmModelClassGetType (modelName);
+        if (modelType < 0) psAbort("unknown model %s", modelName);
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+    }
+    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
+        // we need to load BOTH a psf and an ext model
+        psf = psphotLoadPSF (config, view, recipe);
+        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
+
+        // find the model: supplied by user or first in the PSF_MODEL list
+        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+        if (!status || !strcasecmp (modelName, "DEFAULT")) {
+            // get the list pointers for the PSF_MODEL entries
+
+            psList *list = NULL;
+            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+            if (mdi->type == PS_DATA_STRING) {
+                list = psListAlloc(NULL);
+                psListAdd (list, PS_LIST_HEAD, mdi);
+            } else {
+                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+                list = psMemIncrRefCounter(mdi->data.list);
+            }
+
+            // take the first list element
+            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
+            modelName = item->data.V;
+        }
+        modelType = pmModelClassGetType (modelName);
+        if (modelType < 0) psAbort("unknown model %s", modelName);
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+    }
+
+    // find the local sky
+    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
+    if (!status) psAbort("pmSourceLocalSky error");
+
+    // get the source moments
+    status = pmSourceMoments (source, mRADIUS, 0.0, 1.0, maskVal);
+    if (!status) psAbort("psSourceMoments error");
+    source->peak->value = source->moments->Peak;
+
+    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->Mx, source->moments->My);
+    fprintf (stderr, "moments: %f, %f - %f\n", source->moments->Mxx, source->moments->Myy, source->moments->Mxy);
+
+    psEllipseMoments moments;
+    moments.x2 = source->moments->Mxx;
+    moments.y2 = source->moments->Myy;
+    moments.xy = source->moments->Mxy;
+    psEllipseAxes axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    fprintf (stderr, "axes: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    // get the initial model parameter guess
+    pmModel *model = pmSourceModelGuess (source, modelType);
+    source->modelEXT = model;
+
+    // if any parameters are defined by the user, take those values
+    int nParams = pmModelClassParameterCount (modelType);
+    psF32 *params = model->params->data.F32;
+    params[PM_PAR_XPOS] = xObj; // XXX use the user-supplied value,
+    params[PM_PAR_YPOS] = yObj; // XXX or use the centroid
+    for (int i = 0; i < nParams; i++) {
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+
+        sprintf (name, "TEST_FIT_PAR%d", i);
+        value = psMetadataLookupF32 (&status, recipe, name);
+        if (status && isfinite (value)) {
+            params[i] = value;
+        }
+    }
+
+    float area = params[4]*params[5];
+    fprintf (stderr, "peak: %f @ (%f, %f)\n", source->moments->Sum*area, (double)source->peak->x, (double)source->peak->y);
+
+    // for PSF fitting, set the shape parameters based on the PSF & source position
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+        source->modelPSF = pmModelFromPSF (model, psf);
+        psFree (model);
+        model = source->modelPSF;
+        params = model->params->data.F32;
+    }
+
+    // list model input shape
+    psEllipseShape shape;
+    shape.sx  = 1.4 / model->params->data.F32[4];
+    shape.sy  = 1.4 / model->params->data.F32[5];
+    shape.sxy = model->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    fprintf (stderr, "guess: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    fprintf (stderr, "input parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // define the pixels used for the fit
+    psImageKeepCircle (source->maskObj, xObj, yObj, RADIUS, "OR", markVal);
+    psphotSaveImage (NULL, source->maskObj, "mask1.fits");
+
+    char *fitset = psMetadataLookupStr (&status, recipe, "TEST_FIT_SET");
+    if (status) {
+        status = psphotFitSet (source, model, fitset, fitMode, maskVal);
+        exit (0);
+    }
+
+    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
+        // build the psf for the object
+        source->modelPSF = pmModelFromPSF (model, psf);
+        source->modelEXT = model;
+
+	// what fraction of the PSF is used? (radius in pixels : 2 -> 5x5 box)
+	int psfSize = psMetadataLookupS32 (&status, recipe, "PCM_BOX_SIZE");
+	assert (status);
+
+        model = psphotPSFConvModel (readout, source, modelType, maskVal, markVal, psfSize);
+        params = model->params->data.F32;
+    } else {
+        status = pmSourceFitModel (source, model, fitMode, maskVal);
+    }
+
+    // measure the source mags
+    pmSourcePhotometryModel (&fitMag, model);
+    pmSourcePhotometryAper  (&obsMag, model, source->pixels, source->maskObj, maskVal);
+    fprintf (stderr, "ap: %f, fit: %f, apmifit: %f, nIter: %d\n", obsMag, fitMag, obsMag - fitMag, model->nIter);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    // pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+    fprintf (stderr, "output parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // write out
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+
+    psLogMsg ("psphot", PS_LOG_INFO, "model test : %f sec\n", psTimerMark ("modelTest"));
+
+    exit (0);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelWithPSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelWithPSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotModelWithPSF.c	(revision 27622)
@@ -0,0 +1,417 @@
+# include "psphotInternal.h"
+# define SAVE_IMAGES 0
+
+bool psphotModelWithPSF_LMM (
+    psMinimization *min,
+    psImage *covar,
+    psVector *params,
+    psMinConstraint *constraint,
+    pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func)
+{
+    psTrace("psphot", 3, "---- begin ----\n");
+    PS_ASSERT_PTR_NON_NULL(min, false);
+    PS_ASSERT_VECTOR_NON_NULL(params, false);
+    PS_ASSERT_VECTOR_NON_EMPTY(params, false);
+    PS_ASSERT_VECTOR_TYPE(params, PS_TYPE_F32, false);
+    psVector *paramMask = NULL;
+    if (constraint != NULL) {
+        paramMask = constraint->paramMask;
+        if (paramMask != NULL) {
+          PS_ASSERT_VECTOR_TYPE(paramMask, PS_TYPE_VECTOR_MASK, false);
+            PS_ASSERT_VECTORS_SIZE_EQUAL(params, paramMask, false);
+        }
+    }
+    PS_ASSERT_PTR_NON_NULL(func, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    psMinimizeLMLimitFunc checkLimits = NULL;
+    if (constraint) {
+        checkLimits = constraint->checkLimits;
+    }
+
+    // this function has test values and current values for several things
+    // the current value is in lower case
+    // the test value is in upper case
+
+    // allocate internal arrays (current vs Guess)
+    psImage *Alpha = NULL;
+    psVector *Beta = NULL;
+
+    // Alpha & Beta only contain elements to represent the unmasked parameters
+    if (!psMinLM_AllocAB (&Alpha, &Beta, params, paramMask)) {
+        psAbort ("programming error: no unmasked parameters to be fit\n");
+    }
+
+    // allocate internal arrays (current vs Guess)
+    psImage *alpha   = psImageAlloc(Alpha->numCols, Alpha->numRows, PS_TYPE_F32);
+    psVector *beta   = psVectorAlloc(Beta->n, PS_TYPE_F32);
+    psVector *Params = psVectorAlloc(params->n, PS_TYPE_F32);
+
+    psF32 Chisq = 0.0;
+    psF32 lambda = 0.001;
+    psF32 dLinear = 0.0;
+
+    // generate PCM data storage structure
+    pmPCMData *pcm = pmPCMDataAlloc (params, paramMask, source);
+
+    // calculate initial alpha and beta, set chisq (min->value)
+    min->value = psphotModelWithPSF_SetABX(alpha, beta, params, paramMask, pcm, source, psf, func);
+    if (isnan(min->value)) {
+        min->iter = min->maxIter;
+        return(false);
+    }
+    // dump some useful info if trace is defined
+    if (psTraceGetLevel("psphot") >= 6) {
+        p_psImagePrint(psTraceGetDestination(), alpha, "alpha guess (0)");
+        p_psVectorPrint(psTraceGetDestination(), beta, "beta guess (0)");
+    }
+    if (psTraceGetLevel("psphot") >= 5) {
+        p_psVectorPrint(psTraceGetDestination(), params, "params guess (0)");
+    }
+
+    // iterate until the tolerance is reached, or give up
+    while ((min->iter < min->maxIter) && ((min->lastDelta > min->tol) || !isfinite(min->lastDelta))) {
+        psTrace("psphot", 5, "Iteration number %d.  (max iterations is %d).\n", min->iter, min->maxIter);
+        psTrace("psphot", 5, "Last delta is %f.  Min->tol is %f.\n", min->lastDelta, min->tol);
+
+
+        // set a new guess for Alpha, Beta, Params
+        if (!psMinLM_GuessABP(Alpha, Beta, Params, alpha, beta, params, paramMask, checkLimits, lambda, &dLinear)) {
+            min->iter ++;
+            lambda *= 10.0;
+            continue;
+        }
+
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel("psphot") >= 6) {
+            p_psImagePrint(psTraceGetDestination(), Alpha, "Alpha guess (1)");
+            p_psVectorPrint(psTraceGetDestination(), Beta, "Beta guess (1)");
+            p_psVectorPrint(psTraceGetDestination(), beta, "beta current (1)");
+        }
+        if (psTraceGetLevel("psphot") >= 5) {
+            p_psVectorPrint(psTraceGetDestination(), Params, "params guess (1)");
+        }
+
+        // calculate Chisq for new guess, update Alpha & Beta
+        Chisq = psphotModelWithPSF_SetABX(Alpha, Beta, Params, paramMask, pcm, source, psf, func);
+        if (isnan(Chisq)) {
+            min->iter ++;
+            lambda *= 10.0;
+            continue;
+        }
+
+        // convergence criterion:
+        // compare the delta (min->value - Chisq) with the
+        // expected delta from the linear model (dLinear)
+        // accept new guess if it is an improvement (rho > 0), or else increase lambda
+        psF32 rho = (min->value - Chisq) / dLinear;
+
+        psTrace("psphot", 5, "last chisq: %f, new chisq %f, delta: %f, rho: %f\n", min->value,
+                Chisq, min->lastDelta, rho);
+
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel("psphot") >= 6) {
+            p_psImagePrint(psTraceGetDestination(), Alpha, "alpha guess (2)");
+            p_psVectorPrint(psTraceGetDestination(), Beta, "beta guess (2)");
+        }
+
+        /* if (Chisq < min->value) {  */
+        if (rho > 0.0) {
+            min->lastDelta = (min->value - Chisq) / (source->pixels->numCols*source->pixels->numRows - params->n);
+            min->value = Chisq;
+            alpha  = psImageCopy(alpha, Alpha, PS_TYPE_F32);
+            beta   = psVectorCopy(beta, Beta, PS_TYPE_F32);
+            params = psVectorCopy(params, Params, PS_TYPE_F32);
+            lambda *= 0.25;
+
+            // save the new convolved model image
+            psFree (source->modelFlux);
+            source->modelFlux = pmPCMDataSaveImage(pcm);
+        } else {
+            lambda *= 10.0;
+        }
+        min->iter++;
+    }
+    psTrace("psphot", 5, "chisq: %f, last delta: %f, Niter: %d\n", min->value, min->lastDelta, min->iter);
+
+    // construct & return the covariance matrix (if requested)
+    if (covar != NULL) {
+        if (!psMinLM_GuessABP(Alpha, Beta, Params, alpha, beta, params, paramMask, NULL, 0.0, NULL)) {
+            psTrace ("psphot", 5, "failure to calculate covariance matrix\n");
+        }
+        // set covar values which are not masked
+        psImageInit (covar, 0.0);
+        for (int j = 0, J = 0; j < params->n; j++) {
+            if (paramMask && (paramMask->data.PS_TYPE_VECTOR_MASK_DATA[j])) {
+                covar->data.F32[j][j] = 1.0;
+                continue;
+            }
+            for (int k = 0, K = 0; k < params->n; k++) {
+                if (paramMask && (paramMask->data.PS_TYPE_VECTOR_MASK_DATA[k])) continue;
+                covar->data.F32[j][k] = Alpha->data.F32[J][K];
+                K++;
+            }
+            J++;
+        }
+    }
+
+    // free the internal temporary data
+    psFree(alpha);
+    psFree(Alpha);
+    psFree(beta);
+    psFree(Beta);
+    psFree(Params);
+    psFree(pcm);
+
+    if (min->iter == min->maxIter) {
+        psTrace("psphot", 3, "---- end (false) ----\n");
+        return(false);
+    }
+
+    psTrace("psphot", 3, "---- end (true) ----\n");
+    return(true);
+}
+
+psF32 psphotModelWithPSF_SetABX(
+    psImage  *alpha,
+    psVector *beta,
+    const psVector *params,
+    const psVector *paramMask,
+    pmPCMData *pcm,
+    const pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func)
+{
+    // XXX: Check vector sizes.
+    PS_ASSERT_IMAGE_NON_NULL(alpha, NAN);
+    PS_ASSERT_VECTOR_NON_NULL(beta, NAN);
+    PS_ASSERT_VECTOR_NON_NULL(params, NAN);
+
+    PS_ASSERT_PTR_NON_NULL(source, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->pixels, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->variance, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->maskObj, NAN);
+
+    PS_ASSERT_VECTOR_TYPE(params, PS_TYPE_F32, false);
+    if (paramMask) {
+        PS_ASSERT_VECTOR_TYPE(paramMask, PS_TYPE_VECTOR_MASK, false);
+    }
+
+    // 1 *** generate the model and derivative images for this parameter set
+
+    // storage for model derivatives
+    psVector *deriv = psVectorAlloc(params->n, PS_TYPE_F32);
+
+    // working vector to store local coordinate
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    psImageInit (pcm->model, 0.0);
+    for (int n = 0; n < params->n; n++) {
+        if (!pcm->dmodels->data[n]) continue;
+        psImageInit (pcm->dmodels->data[n], 0.0);
+    }
+
+    // fill in the coordinate and value entries
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+
+            // XXX can we skip some of the data points where the model
+            // is not going to be fitted??
+
+            // skip masked points
+            // XXX probably should not skipped masked points:
+            // XXX skip if convolution of unmasked pixels will not see this pixel
+            // if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[i][j]) {
+            // continue;
+            // }
+
+            // skip zero-variance points
+            // XXX why is this not masked?
+            // if (source->variance->data.F32[i][j] == 0) {
+            // continue;
+            // }
+            // skip nan value points
+            // XXX why is this not masked?
+            // if (!isfinite(source->pixels->data.F32[i][j])) {
+            // continue;
+            // }
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+
+            pcm->model->data.F32[i][j] = func (deriv, params, coord);
+
+            for (int n = 0; n < params->n; n++) {
+                if ((paramMask != NULL) && (paramMask->data.PS_TYPE_VECTOR_MASK_DATA[n])) { continue; }
+                psImage *dmodel = pcm->dmodels->data[n];
+                dmodel->data.F32[i][j] = deriv->data.F32[n];
+            }
+        }
+    }
+    psFree(coord);
+    psFree(deriv);
+
+    // convolve model and dmodel arrays with PSF
+    psImageConvolveDirect (pcm->modelConv, pcm->model, psf);
+    for (int n = 0; n < pcm->dmodels->n; n++) {
+        if (pcm->dmodels->data[n] == NULL) continue;
+        psImage *dmodel = pcm->dmodels->data[n];
+        psImage *dmodelConv = pcm->dmodelsConv->data[n];
+        psImageConvolveDirect (dmodelConv, dmodel, psf);
+    }
+
+    // XXX TEST : SAVE IMAGES
+# if (SAVE_IMAGES)
+    psphotSaveImage (NULL, psf->image, "psf.fits");
+    psphotSaveImage (NULL, pcm->model, "model.fits");
+    psphotSaveImage (NULL, pcm->modelConv, "modelConv.fits");
+    psphotSaveImage (NULL, source->pixels, "obj.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+    psphotSaveImage (NULL, source->variance, "variance.fits");
+# endif
+
+    // 2 *** accumulate alpha & beta
+
+    // zero alpha and beta for summing below
+    psImageInit (alpha, 0.0);
+    psVectorInit (beta, 0.0);
+    float chisq = 0.0;
+
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            // XXX are we doing the right thing with the mask?
+            // skip masked points
+            if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[i][j]) {
+                continue;
+            }
+            // skip zero-variance points
+            if (source->variance->data.F32[i][j] == 0) {
+                continue;
+            }
+            // skip nan value points
+            if (!isfinite(source->pixels->data.F32[i][j])) {
+                continue;
+            }
+
+            float ymodel  = pcm->modelConv->data.F32[i][j];
+            float yweight = 1.0 / source->variance->data.F32[i][j];
+            float delta = ymodel - source->pixels->data.F32[i][j];
+
+            chisq += PS_SQR(delta) * yweight;
+
+            if (isnan(delta)) psAbort("nan in delta");
+            if (isnan(chisq)) psAbort("nan in chisq");
+
+            // alpha & beta only contain unmasked elements
+            for (int n1 = 0, N1 = 0; n1 < params->n; n1++) {
+                if ((paramMask != NULL) && (paramMask->data.PS_TYPE_VECTOR_MASK_DATA[n1])) continue;
+                psImage *dmodel = pcm->dmodelsConv->data[n1];
+                float weight = dmodel->data.F32[i][j] * yweight;
+                for (int n2 = 0, N2 = 0; n2 <= n1; n2++) {
+                    if ((paramMask != NULL) && (paramMask->data.PS_TYPE_VECTOR_MASK_DATA[n2])) continue;
+                    dmodel = pcm->dmodelsConv->data[n2];
+                    alpha->data.F32[N1][N2] += weight * dmodel->data.F32[i][j];
+                    N2++;
+                }
+                beta->data.F32[N1] += weight * delta;
+                N1++;
+            }
+        }
+    }
+
+    // calculate lower-left half of alpha
+    for (psS32 j = 1; j < alpha->numCols; j++) {
+        for (psS32 k = 0; k < j; k++) {
+            alpha->data.F32[k][j] = alpha->data.F32[j][k];
+        }
+    }
+
+    return(chisq);
+}
+
+static void pmPCMDataFree (pmPCMData *pcm) {
+
+    if (pcm == NULL) return;
+
+    psFree (pcm->model);
+    psFree (pcm->modelConv);
+    psFree (pcm->dmodels);
+    psFree (pcm->dmodelsConv);
+    return;
+}
+
+pmPCMData *pmPCMDataAlloc (
+    const psVector *params,
+    const psVector *paramMask,
+    pmSource *source) {
+
+    pmPCMData *pcm = (pmPCMData *) psAlloc(sizeof(pmPCMData));
+    psMemSetDeallocator(pcm, (psFreeFunc) pmPCMDataFree);
+
+    // Allocate storage images for raw model and derivative images
+    pcm->model = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    pcm->dmodels = psArrayAlloc (params->n);
+    for (psS32 n = 0; n < params->n; n++) {
+        pcm->dmodels->data[n] = NULL;
+        if ((paramMask != NULL) && (paramMask->data.PS_TYPE_VECTOR_MASK_DATA[n])) { continue; }
+        pcm->dmodels->data[n] = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    }
+
+    // Allocate storage images for convolved model and derivative images
+    pcm->modelConv = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    pcm->dmodelsConv = psArrayAlloc (params->n);
+    for (psS32 n = 0; n < params->n; n++) {
+        pcm->dmodelsConv->data[n] = NULL;
+        if ((paramMask != NULL) && (paramMask->data.PS_TYPE_VECTOR_MASK_DATA[n])) { continue; }
+        pcm->dmodelsConv->data[n] = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    }
+
+    return pcm;
+}
+
+psImage *pmPCMDataSaveImage (pmPCMData *pcm) {
+
+    psImage *model = psImageCopy (NULL, pcm->modelConv, PS_TYPE_F32);
+
+    return model;
+}
+
+/*
+ *
+ * we have a function func(param; value)
+
+ * basic LMM:
+
+ - fill in the data (x, y)
+
+ chisq = SetABX (alpha, beta, params, paramMask, x, y, dy, func)
+
+ while () {
+ GuessABP (Alpha, Beta, Params, alpha, beta, params, paramMask, checkLimits, lambda)
+ dLinear = dLinear(Beta, beta, lambda);
+ chisq = SetABX (alpha, beta, params, paramMask, x, y, dy, func)
+ convergence tests...
+ }
+
+
+
+ ** GuessABP:
+
+ f_c = sum_i (kern_i * func (x_i; p_o))
+
+ df_c/dp_o = d/dp_o [sum_i (kern_i * func (x_i; p_o))]
+
+ df_c/dp_o = sum_i (d/dp_o [kern_i * func (x_i; p_o)])
+
+ df_c/dp_o = sum_i (kern_i * d/dp_o [func (x_i; p_o)])
+
+ - generate image arrays for func, dfunc/dp_j (not masked)
+ - convolve each with psf
+ - measure delta = f_conv - data
+ - etc
+*/
+
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMomentsStudy.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMomentsStudy.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMomentsStudy.c	(revision 27622)
@@ -0,0 +1,171 @@
+# include "psphotInternal.h"
+
+int SetUpKapa (Graphdata *graphdata);
+int PlotVectors (int kapa, Graphdata *graphdata, psVector *inVector, psVector *outVector);
+int PlotModelSet (int kapa, Graphdata *graphdata, pmReadout *readout, char *name, float par7);
+
+FILE *output = NULL;
+
+int main (int argc, char **argv) {
+
+  Graphdata graphdata;
+  int kapa;
+
+  pmErrorRegister();                  // register psModule's error codes/messages
+  pmModelClassInit();
+
+  // loop over sigma_window
+  // loop over sigma_input 
+  // generate a fake source using the desired model
+  // add noise?
+  // measure the moments for the object
+  // accumulate Mxx, Myy
+
+  // plot sigma_input vs sigma_output == sqrt(Mxx)
+
+  // pmSourceMoments requires a source with a peak and pixels
+
+  if (argc != 2) {
+    fprintf (stderr, "USAGE: psphotMomentsStudy (output)\n");
+    exit (2);
+  }
+
+  output = fopen (argv[1], "w");
+  if (!output) exit (1);
+  fprintf (output, "# %4s %5s  %6s %6s %6s\n", "type", "par7", "sigWin", "sigIn", "sigOut");
+
+  kapa = SetUpKapa(&graphdata);
+
+  // create a containing image & associated readout
+  pmReadout *readout = pmReadoutAlloc(NULL);
+  readout->image = psImageAlloc(128, 128, PS_TYPE_F32);
+
+  // create a dummy variance, but don't populate -- it is not used by pmSourceMoments if the sigma parameters is set to 0.0
+  readout->variance = psImageAlloc(128, 128, PS_TYPE_F32);
+
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_GAUSS",  0.0);
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_PGAUSS", 0.0);
+
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_PS1_V1", 0.3);
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_PS1_V1", 1.0);
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_PS1_V1", 3.0);
+
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_SERSIC", 0.125);
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_SERSIC", 0.250);
+  PlotModelSet (kapa, &graphdata, readout, "PS_MODEL_SERSIC", 0.500);
+
+  // pause and wait for user input:
+  // continue, save (provide name), ??
+  char key[10];
+  fprintf (stdout, "[c]ontinue? ");
+  if (!fgets(key, 8, stdin)) {
+    psWarning("Unable to read option");
+  }
+  fclose (output);
+
+  // psphotSaveImage(NULL, readout->image, "test.fits");
+  exit (0);
+}
+
+int SetUpKapa (Graphdata *graphdata) {
+
+  int kapa = KapaOpenNamedSocket ("kapa", "psphot");
+
+  KapaClearPlots (kapa);
+  KapaInitGraph (graphdata);
+  KapaSetFont (kapa, "courier", 14);
+
+  graphdata->color = KapaColorByName ("black");
+  graphdata->xmin = -0.1;
+  graphdata->xmax =  1.6;
+  graphdata->ymin = -0.1;
+  graphdata->ymax =  1.6;
+  // graphdata->xmax = 10.1;
+  // graphdata->ymax = 10.1;
+  KapaSetLimits (kapa, graphdata);
+
+  KapaBox (kapa, graphdata);
+  // KapaSendLabel (kapa, "&ss&h_in| (pixels)", KAPA_LABEL_XM);
+  // KapaSendLabel (kapa, "&ss&h_out| (pixels)", KAPA_LABEL_YM);
+  KapaSendLabel (kapa, "&ss&h_out| / &ss&h_window|", KAPA_LABEL_XM);
+  KapaSendLabel (kapa, "&ss&h_out| / &ss&h_in|", KAPA_LABEL_YM);
+
+  return (kapa);
+}
+
+int PlotVectors (int kapa, Graphdata *graphdata, psVector *inVector, psVector *outVector) {
+
+  graphdata->color = KapaColorByName ("black");
+  graphdata->ptype = 2;
+  graphdata->size = 1.0;
+  graphdata->style = 2;
+  KapaPrepPlot (kapa, inVector->n, graphdata);
+  KapaPlotVector (kapa, inVector->n, inVector->data.F32, "x");
+  KapaPlotVector (kapa, inVector->n, outVector->data.F32, "y");
+
+  return 0;
+}
+
+
+int PlotModelSet (int kapa, Graphdata *graphdata, pmReadout *readout, char *name, float par7) {
+
+  // create a model & associated source
+  // pmModelType type = pmModelClassGetType("PS_MODEL_GAUSS");
+
+  pmModelType type = pmModelClassGetType(name);
+  pmModel *model = pmModelAlloc(type);
+
+  // set the model parameters
+  model->params->data.F32[PM_PAR_SKY]  = 0.0;
+  model->params->data.F32[PM_PAR_I0]   = 100.0;
+  model->params->data.F32[PM_PAR_XPOS] = 64.0;
+  model->params->data.F32[PM_PAR_YPOS] = 64.0;
+
+  model->params->data.F32[PM_PAR_SXX]  = 1.0 * M_SQRT2;
+  model->params->data.F32[PM_PAR_SYY]  = 1.0 * M_SQRT2;
+  model->params->data.F32[PM_PAR_SXY]  = 0.0;
+
+  if (model->params->n > 7) {
+    model->params->data.F32[PM_PAR_7]  = par7;
+  }
+
+  pmSource *source = pmSourceFromModel(model, readout, 32.0, PM_SOURCE_TYPE_STAR);
+
+  psVector *inVector  = psVectorAllocEmpty(100, PS_TYPE_F32);
+  psVector *outVector = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+  for (float sigWindow = 1.0; sigWindow < 10.0; sigWindow += 1.0) {
+
+    for (float sigIn = 0.5; sigIn < 10.0; sigIn += 0.1) {
+
+      model->params->data.F32[PM_PAR_SXX]  = sigIn * M_SQRT2;
+      model->params->data.F32[PM_PAR_SYY]  = sigIn * M_SQRT2;
+
+      // generate the modelFlux 
+      pmSourceCacheModel(source, 0);
+
+      // instantiate the source
+      pmSourceAdd(source, PM_MODEL_OP_FUNC, 0); 
+
+      pmSourceMoments (source, 32.0, sigWindow, 0.0, 0xffff);
+      // fprintf (stderr, "sigOut : %f\n", sqrt(source->moments->Mxx));
+
+      psVectorAppend(inVector,  sqrt(source->moments->Mxx) / sigWindow);
+      psVectorAppend(outVector, sqrt(source->moments->Mxx) / sigIn);
+
+      psImageInit (readout->image, 0.0);
+
+      fprintf (output, "  %4d %5.2f  %6.3f %6.3f %6.3f\n", type, par7, sigWindow, sigIn, sqrt(source->moments->Mxx));
+    }    
+  }
+
+  PlotVectors (kapa, graphdata, inVector, outVector);
+  inVector->n = 0;
+  outVector->n = 0;
+
+  psFree (source);
+  psFree (inVector);
+  psFree (outVector);
+
+  return 1;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMosaicChip.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMosaicChip.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMosaicChip.c	(revision 27622)
@@ -0,0 +1,39 @@
+# include "psphotInternal.h"
+
+bool psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile)
+{
+    bool status;                        // Status of MD lookup
+
+    pmFPAfile *in = psMetadataLookupPtr(&status, config->files, inFile); // Input file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmFPAfile *out = psMetadataLookupPtr(&status, config->files, outFile); // Output file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // XXXX we are failing to get the output hdu right
+    pmChip *outChip = pmFPAviewThisChip(view, out->fpa);
+    pmChip *inChip = pmFPAviewThisChip(view, in->fpa);
+    if (!outChip->hdu && !outChip->parent->hdu) {
+        const char *name = psMetadataLookupStr(&status, in->fpa->concepts, "FPA.OBS"); // Name of FPA
+        pmFPAAddSourceFromView(out->fpa, name, view, out->format);
+    }
+
+    psImageMaskType blankMask = pmConfigMaskGet("BLANK", config);
+
+    // mosaic the chip, forcing a deep copy (resulting images are not subimages)
+    psTrace("pmChipMosaic", 5, "mosaic chip %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+            in->name, out->name, in->xBin, in->yBin, out->xBin, out->yBin);
+    status = pmChipMosaic(outChip, inChip, true, blankMask);
+    return status;
+}
+
+// XXX does this do everything needed?
+// * mask & weight
+// * loaded PSF model (in readout->analysis)
+// * loaded SRC sources (in readout->analysis)
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotMosaicSubimage.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotMosaicSubimage.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotMosaicSubimage.c	(revision 27622)
@@ -0,0 +1,55 @@
+# include "psphotInternal.h"
+
+// insert the source image into the outimage at Xo, Yo
+bool psphotMosaicSubimage (psImage *outImage, pmSource *source, int Xo, int Yo, int DX, int DY, bool normalize) {
+
+    psRegion inRegion, outRegion;
+    psImage *inImage = source->pixels;
+
+    // identify the region in the output image
+    outRegion = psRegionSet (Xo, Xo + DX, Yo, Yo + DX);
+    outRegion = psRegionForImage (outImage, outRegion);
+    int DXo = outRegion.x1 - outRegion.x0;
+    int DYo = outRegion.y1 - outRegion.y0;
+    if (DXo <= 0) return false;
+    if (DYo <= 0) return false;
+    
+    // center the input source in the output box
+    int dX = (DXo - 1) / 2;
+    int dY = (DYo - 1) / 2;
+
+    // int xo = inImage->col0 + inImage->numCols / 2;
+    // int yo = inImage->row0 + inImage->numRows / 2;
+
+    int xo = source->peak->xf;
+    int yo = source->peak->yf;
+
+    // adjust region to overlay input image pixels
+    inRegion = psRegionSet (xo - dX, xo + dX + 1, yo - dY, yo + dY + 1);
+    inRegion = psRegionForImage (inImage, inRegion);
+
+    float peak = source->peak->flux;
+
+    psImage *subImage = psImageSubset (inImage, inRegion);
+    if (!subImage) {
+	psErrorClear(); // XXX I think there is an error in psphotVisual that is supplying bad images
+	return false;
+    }
+
+    psImage *newImage = psImageAlloc (subImage->numCols, subImage->numRows, PS_TYPE_F32);
+    for (int iy = 0; iy < newImage->numRows; iy++) {
+	for (int ix = 0; ix < newImage->numCols; ix++) {
+	    if (normalize) {
+		newImage->data.F32[iy][ix] = subImage->data.F32[iy][ix] / peak;
+	    } else {
+		newImage->data.F32[iy][ix] = subImage->data.F32[iy][ix];
+	    }
+	}
+    }
+
+    psImageOverlaySection (outImage, newImage, Xo, Yo, "=");
+
+    psFree (subImage);
+    psFree (newImage);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotOutput.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotOutput.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotOutput.c	(revision 27622)
@@ -0,0 +1,356 @@
+# include "psphotInternal.h"
+
+pmReadout *psphotSelectBackground (pmConfig *config,
+                                   const pmFPAview *view) {
+
+    bool status;
+    pmReadout *background;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKMDL");
+    if (!file) return NULL;
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        background = file->readout;
+    } else {
+        background = pmFPAviewThisReadout (view, file->fpa);
+    }
+    return background;
+}
+
+pmReadout *psphotSelectBackgroundStdev (pmConfig *config,
+                                        const pmFPAview *view) {
+
+    bool status;
+    pmReadout *background;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKMDL.STDEV");
+    if (!file) return NULL;
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        background = file->readout;
+    } else {
+        background = pmFPAviewThisReadout (view, file->fpa);
+    }
+    return background;
+}
+
+// dump source stats for psf stars
+bool psphotDumpStats (psArray *sources, char *stage) {
+
+    char filename[64];
+    snprintf (filename, 64, "psf.%s.dat", stage);
+    FILE *f = fopen (filename, "w");
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+	pmModel *model = source->modelPSF;
+	if (!model) continue;
+
+	// int xc = source->peak->x - source->pixels->col0;
+	// int yc = source->peak->y - source->pixels->row0;
+	// float mcore = source->modelFlux ? source->modelFlux->data.F32[yc][xc] : NAN;
+	// float mpeak = model ? model->params->data.F32[PM_PAR_I0] : NAN;
+	// bool subtracted = source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED;
+	// fprintf (stderr, "%d %d : %d : %f %f : %f %f\n", source->peak->x, source->peak->y, subtracted, source->peak->flux, source->pixels->data.F32[yc][xc], mcore, mpeak); 
+
+	fprintf (f, "%6.1f %6.1f : %6.1f %6.1f : %8.3f %8.3f %8.3f : %f : %f %f %f : %f\n",
+		 source->peak->xf, source->peak->yf, 
+		 model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], 
+		 source->psfMag, source->apMag, source->errMag,
+		 model->params->data.F32[PM_PAR_I0], 
+		 model->params->data.F32[PM_PAR_SXX], model->params->data.F32[PM_PAR_SXY], model->params->data.F32[PM_PAR_SYY], 
+		 model->params->data.F32[PM_PAR_7]);
+    }
+    fclose (f);
+    return true;
+}
+
+// XXX replace this with a call to a pmConfig function (pmConfigDump...)
+bool psphotDumpConfig (pmConfig *config) {
+
+  psMetadataConfigWrite (config->user, "user.md");
+  psMetadataConfigWrite (config->camera, "camera.md");
+  psMetadataConfigWrite (config->recipes, "recipes.md");
+  psMetadataConfigWrite (config->arguments, "arguments.md");
+  psMetadataConfigWrite (config->files, "files.md");
+  return true;
+}
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteImage (fits, NULL, image, 0, NULL);
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+bool psphotDumpMoments (psMetadata *recipe, psArray *sources) {
+
+    bool status;
+
+    // optional dump of all rough source data
+    char *output = psMetadataLookupStr (&status, recipe, "MOMENTS_OUTPUT_FILE");
+    if (!output) return false;
+    if (output[0] == 0) return false;
+    if (!strcasecmp (output, "NONE")) return false;
+
+    pmMomentsWriteText (sources, output);
+    return true;
+}
+
+bool psphotDumpSource (pmSource *source, char *name) {
+
+    FILE *f = fopen (name, "w");
+    if (f == NULL) psAbort("can't open file");
+
+    for (int i = 0; i < source->pixels->numRows; i++) {
+        for (int j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[i][j]) {
+                continue;
+            }
+            // skip zero-variance points
+            if (source->variance->data.F32[i][j] == 0) {
+                continue;
+            }
+
+            fprintf (f, "%d %d %f %f %d\n",
+                     (j + source->pixels->col0),
+                     (i + source->pixels->row0),
+                     source->pixels->data.F32[i][j],
+                     1.0 / source->variance->data.F32[i][j],
+                     source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[i][j]);
+        }
+    }
+    fclose (f);
+    return true;
+}
+
+bool psphotAddPhotcode (pmConfig *config, const pmFPAview *view) {
+
+    bool status = false;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotAddPhotcodeReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to add photcode to PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotAddPhotcodeReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index) {
+
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filerule, index); // File of interest
+    PS_ASSERT (file, false);
+
+    pmReadout  *readout = pmFPAviewThisReadout (view, file->fpa);
+
+    // determine PHOTCODE from fpa & view, overwrite in readout->analysis
+    char *photcode = pmConceptsPhotcodeForView (file, view);
+    PS_ASSERT (photcode, false);
+
+    psMetadataAddStr (readout->analysis, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "photcode from FPA concepts", photcode);
+    psLogMsg ("psphot", 3, "PHOTCODE is %s", photcode);
+
+    psFree (photcode);
+    return true;
+}
+
+bool psphotSetHeaderNstars (psMetadata *header, psArray *sources) {
+
+    int nSrc = 0;
+    int nCR  = 0;
+    int nEXT = 0;
+
+    // count the number of sources which will be written and other sub-types
+    for (int i = 0; (sources != NULL) && (i < sources->n); i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL)
+            continue;
+        if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+            nEXT ++;
+        }
+        if (source->mode & PM_SOURCE_MODE_CR_LIMIT) {
+            nCR ++;
+        }
+        nSrc ++;
+    }
+
+    for (int i = 0; (sources != NULL) && (i < sources->n); i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL)
+            continue;
+        nSrc ++;
+    }
+
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NSTARS",   PS_META_REPLACE, "Number of sources", nSrc);
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NDET_EXT", PS_META_REPLACE, "Number of extended sources", nEXT);
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NDET_CR",  PS_META_REPLACE, "Number of cosmic rays", nCR);
+    return true;
+}
+
+// these values are saved in an output header stub - they are added to either the PHU header
+// (CMP) or the MEF table header (CMF).  before the header is created, each readout has these
+// values stored on readout->analysis
+psMetadata *psphotDefineHeader (psMetadata *analysis) {
+
+    bool status = true;
+
+    psMetadata *header = psMetadataAlloc ();
+
+    // write necessary information to output header
+    psMetadataItemSupplement (&status, header, analysis, "ZERO_PT");
+    psMetadataItemSupplement (&status, header, analysis, "PHOTCODE");
+
+    psMetadataItemSupplement (&status, header, analysis, "APMIFIT");
+    psMetadataItemSupplement (&status, header, analysis, "DAPMIFIT");
+    psMetadataItemSupplement (&status, header, analysis, "NAPMIFIT");
+
+    // PSF model parameters (shape values for image center)
+    psMetadataItemSupplement (&status, header, analysis, "NPSFSTAR");
+    psMetadataItemSupplement (&status, header, analysis, "APLOSS");
+
+    psMetadataItemSupplement (&status, header, analysis, "FWHM_MAJ");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MJ_SG");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MJ_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MJ_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "FWHM_MIN");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MN_SG");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MN_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "FW_MN_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "ANGLE");
+
+    // Image Quality measurements
+    psMetadataItemSupplement (&status, header, analysis, "IQ_NSTAR");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW1");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW1_E");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW2");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_FW2_E");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2_ER");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C_E");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C_L");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2C_U");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S_E");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S_L");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M2S_U");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3_ER");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M3_UQ");
+
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4_ER");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4_LQ");
+    psMetadataItemSupplement (&status, header, analysis, "IQ_M4_UQ");
+
+    // XXX these need to be defined from elsewhere
+    psMetadataAdd (header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+    psMetadataItemSupplement (&status, header, analysis, "NSTARS");
+
+    psMetadataItemSupplement (&status, header, analysis, "NDET_EXT");
+    psMetadataItemSupplement (&status, header, analysis, "NDET_CR");
+
+    // sky background model statistics
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_MN");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_SIG");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_MIN");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_MAX");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_NX");
+    psMetadataItemSupplement (&status, header, analysis, "MSKY_NY");
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "DT_PHOT", PS_META_REPLACE, "elapsed psphot time", psTimerMark ("psphotReadout"));
+
+    return header;
+}
+
+// XXX add args as needed
+bool psphotDumpPSFStars (pmReadout *readout, pmPSFtry *try, float radius, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    psphotSaveImage (NULL, readout->image,  "rawstars.fits");
+
+    for (int i = 0; i < try->sources->n; i++) {
+        // masked for: bad model fit, outlier in parameters
+        if (try->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL)
+            continue;
+
+        pmSource *source = try->sources->data[i];
+        float x = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+        float y = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+
+        // set the mask and subtract the PSF model
+        // XXX should we be using maskObj? should we be unsetting the mask?
+        // use pmModelSub because modelFlux has not been generated
+        assert (source->maskObj);
+        psImageKeepCircle (source->maskObj, x, y, radius, "OR", markVal);
+        pmModelSub (source->pixels, source->maskObj, source->modelPSF, PM_MODEL_OP_FULL, maskVal);
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal));
+    }
+
+    FILE *f = fopen ("shapes.dat", "w");
+    for (int i = 0; i < try->sources->n; i++) {
+        psF32 inPar[10];  // must be psF32 to pmPSF_FitToModel
+
+        // masked for: bad model fit, outlier in parameters
+        if (try->mask->data.PS_TYPE_VECTOR_MASK_DATA[i] & PSFTRY_MASK_ALL) continue;
+
+        pmSource *source = try->sources->data[i];
+        psF32 *outPar = source->modelEXT->params->data.F32;
+
+        psEllipseShape shape;
+
+        shape.sx  = outPar[PM_PAR_SXX] / M_SQRT2;
+        shape.sy  = outPar[PM_PAR_SYY] / M_SQRT2;
+        shape.sxy = outPar[PM_PAR_SXY];
+
+        psEllipsePol pol = pmPSF_ModelToFit (outPar);
+        inPar[PM_PAR_E0] = pol.e0;
+        inPar[PM_PAR_E1] = pol.e1;
+        inPar[PM_PAR_E2] = pol.e2;
+        pmPSF_FitToModel (inPar, 0.1);
+
+        psEllipseAxes axes1 = psEllipseShapeToAxes (shape, 20.0);
+        psEllipseAxes axes2 = psEllipsePolToAxes(pol, 0.1);
+
+        psEllipsePol pol2 = psEllipseAxesToPol (axes1);
+
+        fprintf (f, "%3d  %7.2f %7.2f  %7.4f %7.4f %7.4f  --  %7.4f %7.4f %7.4f  :  %7.4f %7.4f %7.4f  --  %7.4f %7.4f %7.4f : %7.4f %7.4f %6.1f : %7.4f %7.4f %6.1f\n",
+                 i, outPar[PM_PAR_XPOS], outPar[PM_PAR_YPOS],
+                 outPar[PM_PAR_SXX], outPar[PM_PAR_SXY], outPar[PM_PAR_SYY],
+                 pol.e0, pol.e1, pol.e2,
+                 pol2.e0, pol2.e1, pol2.e2,
+                 inPar[PM_PAR_SXX], inPar[PM_PAR_SXY], inPar[PM_PAR_SYY],
+                 axes1.major, axes1.minor, axes1.theta*PM_DEG_RAD,
+                 axes2.major, axes2.minor, axes2.theta*PM_DEG_RAD
+            );
+    }
+    fclose (f);
+
+    psphotSaveImage (NULL, readout->image,  "psfstars.fits");
+    pmSourcesWritePSFs (try->sources, "psfstars.dat");
+    pmSourcesWriteEXTs (try->sources, "extstars.dat", false);
+    // XXX need alternative output function
+    // psMetadata *psfData = pmPSFtoMetadata (NULL, try->psf);
+    // psMetadataConfigWrite (psfData, "psfmodel.dat");
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "wrote out psf-subtracted image, psf data, exiting\n");
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPSFConvModel.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPSFConvModel.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPSFConvModel.c	(revision 27622)
@@ -0,0 +1,141 @@
+# include "psphotInternal.h"
+# define USE_DELTA_PSF 0
+
+// save as static values so they may be set externally
+static psF32 PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = 15;
+static psF32 PM_SOURCE_FIT_MODEL_TOLERANCE = 0.1;
+
+// input source has both modelPSF and modelEXT.  on successful exit, we set the
+// modelConv to contain the fitted parameters, and the modelFlux to contain the 
+// convolved model image.
+pmModel *psphotPSFConvModel (pmReadout *readout, pmSource *source, pmModelType modelType, psImageMaskType maskVal, psImageMaskType markVal, int psfSize) {
+    
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // make sure we save a cached copy of the psf flux
+    pmSourceCachePSF (source, maskVal);
+
+    // convert the cached cached psf model for this source to a psKernel
+    psKernel *psf = psphotKernelFromPSF (source, psfSize);
+    if (!psf) return NULL;
+
+# if (USE_DELTA_PSF)
+    psImageInit (psf->image, 0.0);
+    psf->image->data.F32[(int)(0.5*psf->image->numRows)][(int)(0.5*psf->image->numCols)] = 1.0;
+# endif
+
+    // generate copy of the model
+    // XXX we could modify the parameter values or even the model 
+    // here based on the observed seeing (some lookup table...)
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *modelConv = pmSourceModelGuess (source, modelType);
+    if (!modelConv) {
+	psFree (psf);
+	return NULL;
+    }
+
+    // adjust the pixels based on the footprint
+    float radius = psphotSetRadiusEXT (readout, source, markVal);
+    if (!pmSourceMoments (source, radius, 0.0, 0.0, maskVal)) return false;
+
+    // XXX test : modify the Io, SXX, SYY terms based on the psf SXX, SYY terms:
+    psEllipseShape psfShape;
+    psfShape.sx  = source->modelPSF->params->data.F32[PM_PAR_SXX] / M_SQRT2;
+    psfShape.sxy = source->modelPSF->params->data.F32[PM_PAR_SXY];
+    psfShape.sy  = source->modelPSF->params->data.F32[PM_PAR_SYY] / M_SQRT2;
+    psEllipseAxes psfAxes = psEllipseShapeToAxes (psfShape, 20.0);
+
+    // XXX test : modify the Io, SXX, SYY terms based on the psf SXX, SYY terms:
+    psEllipseShape extShape;
+    extShape.sx  = modelConv->params->data.F32[PM_PAR_SXX] / M_SQRT2;
+    extShape.sxy = modelConv->params->data.F32[PM_PAR_SXY];
+    extShape.sy  = modelConv->params->data.F32[PM_PAR_SYY] / M_SQRT2;
+    psEllipseAxes extAxes = psEllipseShapeToAxes (extShape, 20.0);
+
+    // decrease the initial guess ellipse by psf_minor axis:
+    psEllipseAxes extAxesMod;
+    extAxesMod.major = sqrt (PS_MAX (1.0, PS_SQR(extAxes.major) - PS_SQR(psfAxes.minor)));
+    extAxesMod.minor = sqrt (PS_MAX (1.0, PS_SQR(extAxes.minor) - PS_SQR(psfAxes.minor)));
+    extAxesMod.theta = extAxes.theta;
+
+    psEllipseShape extShapeMod = psEllipseAxesToShape (extAxesMod);
+    modelConv->params->data.F32[PM_PAR_SXX] = extShapeMod.sx * M_SQRT2;
+    modelConv->params->data.F32[PM_PAR_SXY] = extShapeMod.sxy;
+    modelConv->params->data.F32[PM_PAR_SYY] = extShapeMod.sy * M_SQRT2;
+
+    // increase the initial guess central intensity by 2pi r^2:
+    modelConv->params->data.F32[PM_PAR_I0] *= (1.0 + PS_SQR(psfAxes.minor) / PS_SQR(extAxesMod.minor));
+
+    psVector *params  = modelConv->params;
+    psVector *dparams = modelConv->dparams;
+
+    // create the minimization constraints
+    psMinConstraint *constraint = psMinConstraintAlloc();
+    constraint->paramMask = psVectorAlloc (params->n, PS_TYPE_VECTOR_MASK);
+    constraint->checkLimits = modelConv->modelLimits;
+
+    // set parameter mask based on fitting mode
+    // we fit a model without a floating sky term
+    int nParams = params->n - 1;
+    psVectorInit (constraint->paramMask, 0);
+    constraint->paramMask->data.PS_TYPE_VECTOR_MASK_DATA[PM_PAR_SKY] = 1;
+
+    // force the floating parameters to fall within the contraint ranges
+    for (int i = 0; i < params->n; i++) {
+	modelConv->modelLimits (PS_MINIMIZE_PARAM_MIN, i, params->data.F32, NULL);
+	modelConv->modelLimits (PS_MINIMIZE_PARAM_MAX, i, params->data.F32, NULL);
+    }
+
+    // set up the minimization process
+    psMinimization *myMin = psMinimizationAlloc (PM_SOURCE_FIT_MODEL_NUM_ITERATIONS, PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F32);
+
+    bool fitStatus = psphotModelWithPSF_LMM (myMin, covar, params, constraint, source, psf, modelConv->modelFunc);
+    for (int i = 0; i < dparams->n; i++) {
+        if (psTraceGetLevel("psphot") >= 4) {
+            fprintf (stderr, "%f ", params->data.F32[i]);
+        }
+        if ((constraint->paramMask != NULL) && constraint->paramMask->data.PS_TYPE_VECTOR_MASK_DATA[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F32[i][i]);
+    }
+    psTrace ("psphot", 4, "niter: %d, chisq: %f", myMin->iter, myMin->value);
+
+    // renormalize output model image (generated by fitting process)
+    float Io = params->data.F32[PM_PAR_I0];
+    for (int iy = 0; iy < source->modelFlux->numRows; iy++) {
+	for (int ix = 0; ix < source->modelFlux->numCols; ix++) {
+	    source->modelFlux->data.F32[iy][ix] /= Io;
+	}
+    }
+
+    // save the resulting chisq, nDOF, nIter
+    modelConv->chisq = myMin->value;
+    modelConv->nIter = myMin->iter;
+
+    // XXX I actually need to count the number of unmasked pixels here
+    modelConv->nDOF  = source->pixels->numCols*source->pixels->numRows  -  nParams;
+
+    modelConv->flags |= PM_MODEL_STATUS_FITTED;
+    if (!fitStatus) modelConv->flags |= PM_MODEL_STATUS_NONCONVERGE;
+
+    // models can go insane: reject these
+    bool onPic = true;
+    onPic &= (params->data.F32[PM_PAR_XPOS] >= source->pixels->col0);
+    onPic &= (params->data.F32[PM_PAR_XPOS] <  source->pixels->col0 + source->pixels->numCols);
+    onPic &= (params->data.F32[PM_PAR_YPOS] >= source->pixels->row0);
+    onPic &= (params->data.F32[PM_PAR_YPOS] <  source->pixels->row0 + source->pixels->numRows);
+    if (!onPic) modelConv->flags |= PM_MODEL_STATUS_OFFIMAGE;
+
+    source->mode |= PM_SOURCE_MODE_FITTED; // XXX is this needed?
+
+    psFree(psf);
+    psFree(myMin);
+    psFree(covar);
+    psFree(constraint);
+
+    return modelConv;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotParseCamera.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotParseCamera.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotParseCamera.c	(revision 27622)
@@ -0,0 +1,69 @@
+# include "psphotStandAlone.h"
+
+// define the needed / desired I/O files
+bool psphotParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the file to be loaded may have subdivisions at the cell and readout level
+    // we load into pmFPAfile *load, then reformat into pmFPAfile *input
+    pmFPAfile *load = pmFPAfileDefineFromArgs (&status, config, "PSPHOT.LOAD", "INPUT");
+    if (!status) {
+        psError(PSPHOT_ERR_CONFIG, false, "Failed to build FPA from PSPHOT.LOAD");
+        return status;
+    }
+    load->dataLevel = PM_FPA_LEVEL_CHIP; // force load at the CHIP level
+
+    // if MASK or VARIANCE was supplied on command line, bind files to 'load'
+    // the mask and weight will be mosaicked with the image
+    pmFPAfileBindFromArgs (&status, load, config, "PSPHOT.MASK", "MASK");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (!psphotSetMaskBits (config)) {
+        psError (PS_ERR_UNKNOWN, false, "failed to set mask bit values");
+        return NULL;
+    }
+
+    pmFPAfileBindFromArgs (&status, load, config, "PSPHOT.VARIANCE", "VARIANCE");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+
+    // the psphot analysis is performed on chips
+    pmFPAfile *input = pmFPAfileDefineChipMosaic(config, load->fpa, "PSPHOT.INPUT");
+    if (!input) {
+        psError(PSPHOT_ERR_CONFIG, false, _("Unable to generate new file from PSPHOT.INPUT"));
+        return NULL;
+    }
+    // specify the number of psphot input images
+    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.INPUT.NUM", PS_META_REPLACE, "number of inputs", 1);
+
+    // define the additional input/output files associated with psphot
+    if (!psphotDefineFiles (config, input)) {
+        psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files");
+        return false;
+    }
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+        // select on the basis of extname?
+        pmFPASelectChip (load->fpa, -1, true); // deselect all chips
+        for (int i = 0; i < chips->n; i++) {
+            int chipNum = atoi(chips->data[i]);
+            if (! pmFPASelectChip(load->fpa, chipNum, false)) {
+                psError(PSPHOT_ERR_CONFIG, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+                return false;
+            }
+        }
+    }
+    psFree (chips);
+    psTrace("psphot", 1, "Done with psphotParseCamera...\n");
+
+    psErrorClear();                     // some metadata lookup may have failed
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosian.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosian.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosian.c	(revision 27622)
@@ -0,0 +1,32 @@
+# include "psphotInternal.h"
+
+bool psphotPetrosian (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal) {
+
+    // XXX these need to go into recipe values
+    float Rmax = 200;
+
+    psAssert (source->extpars, "need to run psphotRadialProfile first");
+    psAssert (source->extpars->profile, "need to run psphotRadialProfile first");
+
+    // integrate the radial profile for radial bins defined for the petrosian measurement:
+    // SB_i (r_i) where \alpha r_i < r < \beta r_i
+    if (!psphotPetrosianRadialBins (source, Rmax, skynoise)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to generate elliptical profile");
+	return false;
+    }
+  
+    // use the SB_i from above to calculate the petrosian radius and the flux within that radius
+    if (!psphotPetrosianStats (source)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to generate elliptical profile");
+	return false;
+    }
+  
+    psTrace ("psphot", 3, "source at %f,%f: petrosian radius: %f, flux: %f, axis ratio: %f, angle: %f",
+	     source->peak->xf, source->peak->yf, 
+	     source->extpars->petrosian_80->radius, 
+	     source->extpars->petrosian_80->flux, 
+	     source->extpars->profile->axes.minor/source->extpars->profile->axes.major, 
+	     source->extpars->profile->axes.theta*PS_DEG_RAD);
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianAnalysis.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianAnalysis.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianAnalysis.c	(revision 27622)
@@ -0,0 +1,68 @@
+# include "psphotInternal.h"
+
+// aperture-like measurements for extended sources
+bool psphotPetrosianAnalysis (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // XXX temporary user-supplied systematic sky noise measurement (derive from background model)
+    float skynoise = psMetadataLookupF32 (&status, recipe, "SKY.NOISE");
+
+# if (0)
+    // if backModel or backStdev are missing, the values of sky and/or skyErr will be set to NAN
+    // XXX use this to set skynoise
+    pmReadout *backModel = psphotSelectBackground (config, view);
+    pmReadout *backStdev = psphotSelectBackgroundStdev (config, view);
+# endif
+
+    // S/N limit to perform full non-linear fits
+    float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // choose the sources of interest
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+
+	// skip PSF-like and non-astronomical objects
+	if (source->type == PM_SOURCE_TYPE_STAR) continue;
+	if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+	if (source->mode & PM_SOURCE_MODE_DEFECT) continue;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+	if (!(source->mode & PM_SOURCE_MODE_EXT_LIMIT)) continue;
+
+	// limit selection to some SN limit
+	assert (source->peak); // how can a source not have a peak?
+	if (source->peak->SN < SN_LIM) continue;
+
+	// limit selection by analysis region
+	if (source->peak->x < AnalysisRegion.x0) continue;
+	if (source->peak->y < AnalysisRegion.y0) continue;
+	if (source->peak->x > AnalysisRegion.x1) continue;
+	if (source->peak->y > AnalysisRegion.y1) continue;
+
+	// replace object in image
+	if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	}
+
+	psphotPetrosianProfile (readout, source, skynoise);
+
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    psphotVisualShowResidualImage (readout);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianProfile.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianProfile.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianProfile.c	(revision 27622)
@@ -0,0 +1,80 @@
+# include "psphotInternal.h"
+
+// generate the Petrosian radius and flux using elliptical contours
+
+// XXX much of this function is focused on generating the clean contours, which can be used by 
+// any number of aperture-like measurements.  probably will want to rename the pmPetrosian
+// structure to something the pmRadialProfile
+
+bool psphotPetrosianProfile (pmReadout *readout, pmSource *source, float skynoise) {
+
+    // container to hold results from the radial profile analysis
+    pmPetrosian *petrosian = pmPetrosianAlloc();
+
+    // XXX these need to go into recipe values
+    int Nsec = 24;
+    float Rmax = 200;
+    float fluxMin = 0.0;
+    float fluxMax = source->peak->flux;
+
+    // generate a series of radial profiles at Nsec evenly spaced angles.  the profile flux
+    // is measured by interpolation for small radii; for large radii, the pixels in a box
+    // are averaged to increase the S/N (XXX not yet done)
+    if (!psphotRadialProfilesByAngles (source, petrosian, Nsec, Rmax)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to measure radial profile for petrosian");
+	psFree (petrosian);
+	return false;
+    }
+
+    // use the radial profiles to determine the radius of a given isophote.  this isophote
+    // is used to determine the elliptical shape of the object, so it has a relatively high
+    // value (nominally 50% of the peak)
+    if (!psphotRadiiFromProfiles (source, petrosian, fluxMin, fluxMax)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to measure isophotal radii from profiles");
+	psFree (petrosian);
+	return false;
+    }
+
+    // convert the isophotal radius vs angle measurements to an elliptical contour
+    if (!psphotEllipticalContour (source, petrosian)) {
+	psLogMsg ("psphot", 3, "failed to measure elliptical contour");
+	psFree (petrosian);
+	return false;
+    }
+  
+    // generate a single, normalized radial profile following the elliptical contours.
+    // the radius is normalized by the axis ratio so that on the major axis, 1 pixel = 1 pixel
+    if (!psphotEllipticalProfile (source, petrosian)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to generate elliptical profile");
+	psFree (petrosian);
+	return false;
+    }
+  
+    // integrate the radial profile for radial bins defined for the petrosian measurement:
+    // SB_i (r_i) where \alpha r_i < r < \beta r_i
+    if (!psphotPetrosianRadialBins (source, petrosian, Rmax, skynoise)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to generate elliptical profile");
+	psFree (petrosian);
+	return false;
+    }
+  
+    // use the SB_i from above to calculate the petrosian radius and the flux within that radius
+    if (!psphotPetrosianStats (source, petrosian)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to generate elliptical profile");
+	psFree (petrosian);
+	return false;
+    }
+  
+    // XXX this will only work in the psphot context, not the psphotPetrosianStudy...
+    // XXX add the petrosian to the pmSource structure...
+    // psphotVisualShowResidualImage (readout);
+    psphotVisualShowPetrosian (source, petrosian);
+
+    psphotPetrosianFreeVectors(petrosian);
+
+    psTrace ("psphot", 3, "source at %f,%f: petrosian radius: %f, flux: %f, axis ratio: %f, angle: %f",
+	     source->peak->xf, source->peak->yf, petrosian->petrosianRadius, petrosian->petrosianFlux, petrosian->axes.minor/petrosian->axes.major, PS_DEG_RAD*petrosian->axes.theta);
+
+    psFree (petrosian);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianRadialBins.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianRadialBins.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianRadialBins.c	(revision 27622)
@@ -0,0 +1,189 @@
+# include "psphotInternal.h"
+
+// convert the flux vs elliptical radius to annular bins
+
+// we are guaranteed to be limited by either the seeing (1 - few pixels) or by the pixels
+// themselves.  this function does not attempt to measure the radial profiles accurately
+// for radii that are smaller than a minimum (currently 1.0 pixels).  
+
+// for small radii, we are measuring the mean surface brightness in non-overlapping radial
+// bins.  for large radii (r > 2 pixels), we are measuring the surface brightness for a
+// radius range \alpha r_i < i < \beta r_i, but performing this measurement for radii more
+// finely spaced than r_{i+1} = r_i * \beta / \alpha.  for the integration, we need to
+// track the non-overlapping radius values.
+
+// XXX move the resulting elements from profile to extpars->petrosian?
+bool psphotPetrosianRadialBins (pmSource *source, float radiusMax, float skynoise) {
+
+    pmSourceRadialProfile *profile = source->extpars->profile;
+
+    float skyModelErrorSQ = PS_SQR(skynoise);
+
+    psVector *radius = profile->radiusElliptical;
+    psVector *flux = profile->fluxElliptical;
+
+    // sort incoming vectors by radius
+    pmSourceRadialProfileSortPair (radius, flux);
+
+    int nMax = radiusMax;
+
+    // radBin stores the centers of the radial bins, 
+    // radMin, radMax store the bounds
+    psVector *radMin  = psVectorAllocEmpty(nMax, PS_TYPE_F32);
+    psVector *radMax  = psVectorAllocEmpty(nMax, PS_TYPE_F32);
+    psVector *radAlp  = psVectorAllocEmpty(nMax, PS_TYPE_F32);
+    psVector *radBet  = psVectorAllocEmpty(nMax, PS_TYPE_F32);
+
+    psVector *binSB      = psVectorAllocEmpty(nMax, PS_TYPE_F32); // surface brightness of radial bin
+    psVector *binSBstdev = psVectorAllocEmpty(nMax, PS_TYPE_F32); // surface brightness of radial bin
+    psVector *binRad  	 = psVectorAllocEmpty(nMax, PS_TYPE_F32); // mean radius of radial bin
+    psVector *binArea 	 = psVectorAllocEmpty(nMax, PS_TYPE_F32); // area of radial bin (contiguous, non-overlapping)
+
+    psVectorInit (binSB, 0.0);
+    psVectorInit (binSBstdev, 0.0);
+    psVectorInit (binRad, 0.0);
+
+    // generate radial bin bounds
+    radMin->data.F32[0] = 0.0;
+    radMax->data.F32[0] = 1.0;
+    radAlp->data.F32[0] = 0.0;
+    radBet->data.F32[0] = 1.0;
+    
+    radMin->data.F32[1] = 1.0;
+    radMax->data.F32[1] = 1.5;
+    radAlp->data.F32[1] = 1.0;
+    radBet->data.F32[1] = 1.5;
+    
+    radMin->data.F32[2] = 1.5;
+    radMax->data.F32[2] = 2.0;
+    radAlp->data.F32[2] = 1.5;
+    radBet->data.F32[2] = 2.0;
+    
+# define PETROSIAN_ALPHA 0.8
+# define PETROSIAN_BETA 1.25
+# define POWER_LAW_SPACING true
+    
+    // power-law spacing with overlapping boundaries at the geometric mid-points
+    float rBeta = sqrt(PETROSIAN_BETA);
+    for (int i = 3; radBet->data.F32[i-1] < radiusMax; i++) {
+	if (POWER_LAW_SPACING) {
+	    radMin->data.F32[i] = radMax->data.F32[i-1];
+	    radMax->data.F32[i] = radMin->data.F32[i] * PETROSIAN_BETA;
+	    radAlp->data.F32[i] = radMin->data.F32[i] / rBeta;
+	    radBet->data.F32[i] = radMax->data.F32[i] * rBeta;
+	} else {
+	    radMin->data.F32[i] = radMax->data.F32[i-1];
+	    radMax->data.F32[i] = radMin->data.F32[i] + 1;
+	    float rMid = 0.5*(radMin->data.F32[i] + radMax->data.F32[i]);
+	    radAlp->data.F32[i] = rMid * PETROSIAN_ALPHA;
+	    radBet->data.F32[i] = rMid * PETROSIAN_BETA;
+	}
+	radMin->n = radMax->n = radAlp->n = radBet->n = i + 1;
+    }
+
+    // generate radial area-weighted mean radius & non-overlapping areas
+    for (int i = 0; i < radMin->n; i++) {
+	float rMin = radMin->data.F32[i];
+	float rMax = radMax->data.F32[i];
+	
+	float rMin2 = rMin*rMin;
+	float rMin3 = rMin2*rMin;
+
+	float rMax2 = rMax*rMax;
+	float rMax3 = rMax2*rMax;
+
+	float rBin = 2.0 * (rMax3 - rMin3) / (rMax2 - rMin2) / 3.0;
+	
+	// XXX calculate area-weighted radius rather than asserting?
+	binRad->data.F32[i] = rBin;
+	binArea->data.F32[i] = M_PI * (rMax2 - rMin2);
+
+	psTrace ("psphot", 6, "%3d  %5.1f %5.1f : %5.1f : %5.1f %5.1f\n", 
+		 i, radAlp->data.F32[i], radMin->data.F32[i], binRad->data.F32[i],
+		 radMax->data.F32[i], radBet->data.F32[i]);
+    }
+
+    // storage vector for stats
+    psVector *values = psVectorAllocEmpty (flux->n, PS_TYPE_F32);
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    // psStats *stats = psStatsAlloc(PS_STAT_FITTED_MEAN_V4 | PS_STAT_FITTED_STDEV_V4);
+
+    // integrate flux, radius for each of these bins.  since flux is sorted by radius, 
+    // we can do this fairly quickly
+
+    bool done = false;
+    int nOut = 0;
+    float Rmin = radAlp->data.F32[nOut];
+    float Rmax = radBet->data.F32[nOut];
+    float Rnxt = radAlp->data.F32[nOut+1];  // minimum radius for next range
+    int iNext = 0;
+    for (int i = 0; !done && (i < radius->n); i++) {
+	if (radius->data.F32[i] < Rnxt) {
+	  iNext = i;
+	}
+	if (radius->data.F32[i] > Rmax) {
+	    // calculate the value for the nOut bin
+	    float value, dvalue;
+	    if (values->n > 0) {
+		psVectorStats (stats, values, NULL, NULL, 0);
+		value = stats->robustMedian;
+		dvalue = stats->robustStdev;
+	    } else {
+		value = NAN;
+		dvalue = NAN;
+	    }
+	    // binSB->data.F32[nOut] = stats->sampleMedian;
+	    binSB->data.F32[nOut] = value;
+	    binSBstdev->data.F32[nOut] = sqrt(PS_SQR(dvalue) / values->n + skyModelErrorSQ);
+	    // binSB->data.F32[nOut] = stats->fittedMean;
+	    // binSBstdev->data.F32[nOut] = sqrt(PS_SQR(stats->fittedStdev) / values->n + skyModelErrorSQ);
+
+	    // error in the SB is the stdev per bin / sqrt (number of pixels) 
+	    // added in quadrature to a fraction of the local sky (not the 
+	    // residual flux, but the sky from the sky model)
+
+	    psTrace ("psphot", 5, "%3d  %5.1f %5.1f : %5.1f  %5.2f\n", 
+		     nOut, radAlp->data.F32[nOut], radBet->data.F32[nOut], binSB->data.F32[nOut], binSBstdev->data.F32[nOut]);
+
+	    nOut ++;
+	    if (nOut >= radAlp->n) break;
+	    Rmin = radAlp->data.F32[nOut];
+	    Rmax = radBet->data.F32[nOut];
+	    Rnxt = (nOut < nMax - 1) ? radAlp->data.F32[nOut+1] : Rmax;  // minimum radius for next range
+	    values->n = 0;
+	    psStatsInit(stats);
+	    i = iNext;
+	}
+	if (radius->data.F32[i] < Rmin) {
+	    continue;
+	}
+	psVectorAppend (values, flux->data.F32[i]);
+    }
+    binSB->n = binSBstdev->n = binRad->n = binArea->n = nOut;
+    // XXX I think this misses the last radial bin -- do we care?
+
+    // save the vectors
+    profile->radialBins = binRad;
+    profile->area       = binArea;
+    profile->binSB      = binSB;
+    profile->binSBstdev = binSBstdev;
+
+    // psphotPetrosianVisualProfileRadii (radius, flux, binRad, binSB, source->peak->flux, 0.0);
+
+    psFree(radMin);
+    psFree(radMax);
+    psFree(radAlp);
+    psFree(radBet);
+    psFree(values);
+    psFree(stats);
+
+    return true;
+}
+
+// the area-weighted mean radius is given by:
+
+// integral r * 2 pi r dr / integral 2 pi r dr
+
+// = 2/3 pi (r_max^3 - r_min^3)  / pi (r_max^2 - r_min^2) 
+// = 2/3 (r_max^3 - r_min^3) / (r_max^2 - r_min^2)
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianStats.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianStats.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianStats.c	(revision 27622)
@@ -0,0 +1,170 @@
+# include "psphotInternal.h"
+
+# define PETROSIAN_RATIO 0.2
+# define PETROSIAN_RADII 2.0
+
+// generate the Petrosian radius and flux from the mean surface brightness (r_i)
+
+float InterpolateValues (float X0, float Y0, float X1, float Y1, float X);
+
+bool psphotPetrosianStats (pmSource *source) {
+
+    pmSourceRadialProfile *profile = source->extpars->profile;
+
+    float petRadius = NAN;
+    float petFlux = NAN;
+
+    psVector *binSB      = profile->binSB;
+    psVector *binSBstdev = profile->binSBstdev;
+    psVector *binRad     = profile->radialBins;
+    psVector *area       = profile->area;
+
+    psVector *fluxSum     = psVectorAllocEmpty(binSB->n, PS_TYPE_F32);
+    psVector *fluxSumErr2 = psVectorAllocEmpty(binSB->n, PS_TYPE_F32);
+    psVector *refRadius   = psVectorAllocEmpty(binSB->n, PS_TYPE_F32);
+    psVector *petRatio    = psVectorAllocEmpty(binSB->n, PS_TYPE_F32);
+    psVector *petRatioErr = psVectorAllocEmpty(binSB->n, PS_TYPE_F32);
+    psVector *meanSB      = psVectorAllocEmpty(binSB->n, PS_TYPE_F32);
+    psVector *areaSum     = psVectorAllocEmpty(binSB->n, PS_TYPE_F32);
+
+    bool anyPetro = false;
+    bool manyPetro = false;
+    bool above = true;
+    float Asum = 0.0;
+    float Fsum = 0.0;
+    float dFsum2 = 0.0;
+
+    float nSigma = 3.0;
+    int lowestSignificantRadius = 0;
+    float lowestSignificantRatio = 1.0;
+
+    int nOut = 0;
+    for (int i = 0; i < binSB->n; i++) {
+	// skip nan bins (do not contribute to flux or area)
+	if (!isfinite(binSB->data.F32[i])) continue;
+
+	float Area = area->data.F32[i];
+	Asum += Area;
+	Fsum += binSB->data.F32[i] * Area;
+	dFsum2 += PS_SQR(binSBstdev->data.F32[i] * Area);
+
+	float areaInner = 0.5 * Area;
+	float fluxInner = 0.5 * Area * binSB->data.F32[i];
+	float fluxInnerErr2 = PS_SQR(binSBstdev->data.F32[i] * 0.5 * Area);
+	if (nOut > 0) {
+	    areaInner += areaSum->data.F32[nOut-1];
+	    fluxInner += fluxSum->data.F32[nOut-1];
+	    fluxInnerErr2 += fluxSumErr2->data.F32[nOut-1];
+	}
+
+	// ratio = binSB / meanSB
+	// meanSB = flux / area
+	// flux = sum(binSB(i) * area(i)
+	// fluxErr^2 = sum(binSBerr(i)^2 area(i)^2)
+	// meanSBerr^2 = fluxErr^2 / area^2
+	// (ratioErr/ratio)^2 = (binSBerr/binSB)^2 + (meanSBerr/meanSB)^2
+
+	psVectorAppend(meanSB, (fluxInner / areaInner));
+
+	float ratio = binSB->data.F32[i] / meanSB->data.F32[nOut];
+	psVectorAppend(petRatio, ratio);
+
+	float meanSBerr = sqrt(fluxInnerErr2) / areaInner;
+	float ratioErr = fabs(ratio) * sqrt(PS_SQR(binSBstdev->data.F32[i]/binSB->data.F32[i]) + PS_SQR(meanSBerr/meanSB->data.F32[nOut]));
+
+	psVectorAppend(petRatioErr, ratioErr);
+
+	psVectorAppend(areaSum, Asum);
+	psVectorAppend(fluxSum, Fsum);
+	psVectorAppend(fluxSumErr2, dFsum2);
+	psVectorAppend(refRadius, binRad->data.F32[i]);
+
+	psTrace ("psphot", 4, "%3d : %5.2f : %5.3f %5.3f : %5.3f %5.3f : %5.3f %5.3f : %5.3f %5.3f : %5.1f %5.1f\n", 
+		 i, refRadius->data.F32[nOut], 
+		 binSB->data.F32[i], binSBstdev->data.F32[i], 
+		 meanSB->data.F32[nOut], meanSBerr, 
+		 petRatio->data.F32[nOut], petRatioErr->data.F32[nOut], 
+		 fluxSum->data.F32[nOut], sqrt(fluxSumErr2->data.F32[nOut]), areaSum->data.F32[nOut], areaInner);
+    
+	// anytime we transition below the PETROSIAN_RATIO, calculate the radius and flux
+	// we will keep and report the last (largest radius) value
+	if (above && (petRatio->data.F32[nOut] < PETROSIAN_RATIO) && (petRatio->data.F32[nOut] > nSigma*petRatioErr->data.F32[nOut])) {
+	    // interpolate Rvec between i-1 and i to PETROSIAN_RATIO to get flux (Fvec) and radius (rvec)
+	    if (i == 0) { 
+		// assume Fmax @ R = 0.0
+		petRadius = InterpolateValues (1.0, 0.0, petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO);
+	    } else {
+		petRadius = InterpolateValues (petRatio->data.F32[nOut-1], refRadius->data.F32[nOut-1], petRatio->data.F32[nOut], refRadius->data.F32[nOut], PETROSIAN_RATIO);
+	    }
+	    above = false;
+	    if (anyPetro) manyPetro = true;
+	    anyPetro = true;
+	}
+    
+	// anytime we transition below the PETROSIAN_RATIO, calculate the radius and flux
+	// we will keep and report the last (largest radius) value
+	// find the last signficant measurement of the petrosian ratio
+	if (above && (petRatio->data.F32[nOut] < lowestSignificantRatio) && (petRatio->data.F32[nOut] > nSigma*petRatioErr->data.F32[nOut])) {
+	    lowestSignificantRadius = nOut;
+	    lowestSignificantRatio = petRatio->data.F32[nOut];
+	}
+    
+	// reset on transitions up, but do not re-calculate rad_90, flux_90
+	if (!above && (petRatio->data.F32[nOut] >= PETROSIAN_RATIO)) {
+	    above = true;
+	}
+	nOut ++;
+    }
+
+    if (!anyPetro) {
+	// interpolate Rvec between i-1 and i to PETROSIAN_RATIO to get flux (Fvec) and radius (rvec)
+	if (lowestSignificantRadius == 0) { 
+	    // assume Fmax @ R = 0.0
+	    petRadius = InterpolateValues (1.0, 0.0, petRatio->data.F32[lowestSignificantRadius], refRadius->data.F32[lowestSignificantRadius], PETROSIAN_RATIO);
+	} else {
+	    petRadius = InterpolateValues (petRatio->data.F32[lowestSignificantRadius-1], refRadius->data.F32[lowestSignificantRadius-1], petRatio->data.F32[lowestSignificantRadius], refRadius->data.F32[lowestSignificantRadius], PETROSIAN_RATIO);
+	}
+    }
+
+    // now measure the flux within PETROSIAN_RADII * petRadius 
+    float apRadius = PETROSIAN_RADII * petRadius;
+    for (int i = 0; i < refRadius->n; i++) {
+	// XXX use bisection to do this faster:
+	if (refRadius->data.F32[i] > apRadius) {
+	    if (i == 0) {
+		psWarning ("does this case make any sense? (refRadius[0] > apRadius)");
+		continue;
+	    } else {
+		petFlux = InterpolateValues (refRadius->data.F32[i-1], fluxSum->data.F32[i-1], refRadius->data.F32[i], fluxSum->data.F32[i], apRadius);
+		break;
+	    }
+	}
+    }
+
+    if (!source->extpars->petrosian_80) {
+        source->extpars->petrosian_80 = pmSourceExtendedFluxAlloc ();
+    }
+    pmSourceExtendedFlux *petrosian = source->extpars->petrosian_80;
+
+    // XXX save flags (anyPetro, manyPetro)
+    petrosian->radius = petRadius;
+    petrosian->flux   = petFlux;
+
+    // psphotPetrosianVisualStats (binRad, binSB, refRadius, meanSB, petRatio, petRatioErr, fluxSum, petRadius, PETROSIAN_RATIO, petFlux, apRadius);
+
+    psFree(fluxSum);
+    psFree(fluxSumErr2);
+    psFree(refRadius);
+    psFree(petRatio);
+    psFree(petRatioErr);
+    psFree(meanSB);
+    psFree(areaSum);
+
+    return true;
+}
+
+float InterpolateValues (float X0, float Y0, float X1, float Y1, float X) {
+    float Y = Y0 + (Y1 - Y0) * (X - X0) / (X1 - X0);
+    return Y;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianStudy.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianStudy.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianStudy.c	(revision 27622)
@@ -0,0 +1,184 @@
+# include "psphotInternal.h"
+
+# define DX 512
+# define DY 512
+
+// XXX add noise and seeing.
+// XXX double check on sersic functional form
+// XXX modify ratio if ratio > 1.0 (swap major and minor)
+
+pmPeak *psphotLocalPeak(pmReadout *readout, int Xo, int Yo);
+
+int main (int argc, char **argv) {
+
+  pmErrorRegister();                  // register psModule's error codes/messages
+  pmModelClassInit();
+
+  int N;
+  float Xo = 0.5*DX;
+  float Yo = 0.5*DY;
+  char *image = NULL;
+  pmSource *source = NULL;
+
+  float peak = 1000.0;
+  float sigma = 2.0;	      // major axis size 
+  float ARatio = 1.0;
+  float angle = 0.0;
+  float sersic = 0.5;
+  float skynoise = 0.0;
+  
+  if ((N = psArgumentGet (argc, argv, "-peak"))) {
+    psArgumentRemove (N, &argc, argv);
+    peak = atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+  if ((N = psArgumentGet (argc, argv, "-sigma"))) {
+    psArgumentRemove (N, &argc, argv);
+    sigma = atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+  if ((N = psArgumentGet (argc, argv, "-aratio"))) {
+    psArgumentRemove (N, &argc, argv);
+    ARatio = atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+  if ((N = psArgumentGet (argc, argv, "-angle"))) {
+    psArgumentRemove (N, &argc, argv);
+    angle = PS_RAD_DEG*atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+  if ((N = psArgumentGet (argc, argv, "-sersic"))) {
+    psArgumentRemove (N, &argc, argv);
+    sersic = atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+  if ((N = psArgumentGet (argc, argv, "-skynoise"))) {
+    psArgumentRemove (N, &argc, argv);
+    skynoise = atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+  if ((N = psArgumentGet (argc, argv, "-visual"))) {
+    psArgumentRemove (N, &argc, argv);
+    pmVisualSetVisual(true);
+  }
+  if ((N = psArgumentGet (argc, argv, "-coords"))) {
+    psArgumentRemove (N, &argc, argv);
+    Xo = atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+    Yo = atof(argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+  if ((N = psArgumentGet (argc, argv, "-image"))) {
+    psArgumentRemove (N, &argc, argv);
+    image = psStringCopy (argv[N]);
+    psArgumentRemove (N, &argc, argv);
+  }
+
+  if (argc != 1) {
+    fprintf (stderr, "USAGE: psphotPetrosianStudy\n");
+    exit (2);
+  }
+
+  // create a containing image & associated readout
+  pmReadout *readout = pmReadoutAlloc(NULL);
+  if (!image) {
+      readout->image = psImageAlloc(DX, DY, PS_TYPE_F32);
+
+      // create a dummy variance, but don't populate -- it is not used by pmSourceMoments if the sigma parameters is set to 0.0
+      readout->variance = psImageAlloc(DX, DY, PS_TYPE_F32);
+
+      // create a model & associated source
+      pmModelType type = pmModelClassGetType("PS_MODEL_SERSIC");
+      pmModel *model = pmModelAlloc(type);
+
+      // set the model parameters
+      model->params->data.F32[PM_PAR_SKY]  = 0.0;
+      model->params->data.F32[PM_PAR_I0]   = peak;
+      model->params->data.F32[PM_PAR_XPOS] = Xo;
+      model->params->data.F32[PM_PAR_YPOS] = Yo;
+
+      psEllipseAxes axes;
+      axes.major = sigma;
+      axes.minor = sigma*ARatio;
+      axes.theta = angle;
+
+      psEllipseShape shape = psEllipseAxesToShape (axes);
+
+      // XXX set the sigma with user input
+      model->params->data.F32[PM_PAR_SXX]  = shape.sx * M_SQRT2;
+      model->params->data.F32[PM_PAR_SYY]  = shape.sy * M_SQRT2;
+      model->params->data.F32[PM_PAR_SXY]  = shape.sxy;
+
+      if (model->params->n > 7) {
+	  model->params->data.F32[PM_PAR_7]  = sersic;
+      }
+
+      // generate source container & populate image
+      source = pmSourceFromModel(model, readout, Xo, PM_SOURCE_TYPE_STAR);
+
+      // generate the modelFlux 
+      pmSourceCacheModel(source, 0);
+
+      // instantiate the source
+      pmSourceAdd(source, PM_MODEL_OP_FUNC, 0); 
+
+      // XXX add noise here...
+      psphotSaveImage(NULL, readout->image, "sersic.fits");
+
+  } else {
+      psRegion full = psRegionSet(0,0,0,0);
+      psFits *fits = psFitsOpen(image, "r");
+      readout->image = psFitsReadImage(fits, full, 0);
+
+      source = pmSourceAlloc();
+      source->peak = psphotLocalPeak(readout, Xo, Yo);
+      pmSourceDefinePixels (source, readout, Xo, Yo, 128);
+  }
+
+  psphotPetrosianProfile (readout, source, skynoise);
+
+  psFree (source);
+
+  exit (0);
+}
+
+// Xo, Yo are in parent coords
+pmPeak *psphotLocalPeak(pmReadout *readout, int Xo, int Yo) {
+
+    int Xp = Xo;
+    int Yp = Yo;
+    float peakFlux = readout->image->data.F32[Yp][Xp];
+
+    // find local peak within +/- 3 pix of the given coordinate
+    for (int iy = Yo - 3; iy <= Yo + 3; iy++) {
+	for (int ix = Xo - 3; ix <= Xo + 3; ix++) {
+	    if (peakFlux < readout->image->data.F32[iy][ix]) {
+		Xp = ix;
+		Yp = iy;
+		peakFlux = readout->image->data.F32[Yp][Xp];
+	    }
+	}
+    }
+
+    pmPeak *peak = pmPeakAlloc(Xp, Yp, peakFlux, PM_PEAK_LONE);
+
+    // calculate fractional peak position relative to Xp,Yp
+    psPolynomial2D *bicube = psImageBicubeFit (readout->image, Xp, Yp);
+    psPlane min = psImageBicubeMin (bicube);
+    psFree (bicube);
+
+    // if min point is too deviant, use the peak value
+    if ((fabs(min.x) < 1.5) && (fabs(min.y) < 1.5)) {
+        peak->xf = min.x + Xp;
+        peak->yf = min.y + Yp;
+
+	// xf,yf must land on image with 0 pixel border
+	peak->xf = PS_MAX (PS_MIN (peak->xf, readout->image->numCols - 1), readout->image->col0);
+	peak->yf = PS_MAX (PS_MIN (peak->yf, readout->image->numRows - 1), readout->image->row0);
+    } else {
+        peak->xf = Xp;
+        peak->yf = Yp;
+    }
+
+    return peak;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianVisual.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianVisual.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotPetrosianVisual.c	(revision 27622)
@@ -0,0 +1,405 @@
+# include "psphotInternal.h"
+
+// this function displays representative images as the psphot analysis progresses:
+// 0 : image, 1 : variance
+// 0 : backsub, 1 : variance, 2 : backgnd
+// 0 : backsub, 1 : variance, 2 : signif
+// (overlay peaks on images)
+// (overlay footprints on images)
+// (overlay moments on images)
+// (overlay rough class on images)
+// 0 : backsub, 1 : psfpos, 2: psfsub
+// 0 : backsub, 1 : lin_resid, 2: psfsub
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+
+// functions used to visualize the analysis as it goes
+// these are invoked by the -visual options
+
+static int kapa = -1;
+static int kapa2 = -1;
+
+// if no valid data is supplied (NULL or n <- 0), leave limits as they were
+bool pmVisualLimitsFromVectors (Graphdata *graphdata, psVector *xVec, psVector *yVec) {
+
+    if (xVec && xVec->n > 0) {
+	graphdata->xmin = graphdata->xmax = xVec->data.F32[0];
+	for (int i = 1; i < xVec->n; i++) {
+	    if (!isfinite(xVec->data.F32[i])) continue;
+	    graphdata->xmin = PS_MIN (graphdata->xmin, xVec->data.F32[i]);
+	    graphdata->xmax = PS_MAX (graphdata->xmax, xVec->data.F32[i]);
+	}
+	float range = graphdata->xmax - graphdata->xmin;
+	graphdata->xmax += 0.05*range;
+	graphdata->xmin -= 0.05*range;
+    }
+    if (yVec && yVec->n > 0) {
+	graphdata->ymin = graphdata->ymax = yVec->data.F32[0];
+	for (int i = 1; i < yVec->n; i++) {
+	    if (!isfinite(yVec->data.F32[i])) continue;
+	    graphdata->ymin = PS_MIN (graphdata->ymin, yVec->data.F32[i]);
+	    graphdata->ymax = PS_MAX (graphdata->ymax, yVec->data.F32[i]);
+	}
+	float range = graphdata->ymax - graphdata->ymin;
+	graphdata->ymax += 0.05*range;
+	graphdata->ymin -= 0.05*range;
+    }
+    return true;
+}
+
+bool psphotPetrosianVisualProfileByAngle (psVector *radius, psVector *flux) {
+
+    Graphdata graphdata;
+
+    // return true;
+    if (!pmVisualIsVisual()) return true;
+
+    if (kapa2 == -1) {
+        kapa2 = KapaOpenNamedSocket ("kapa", "psphot:plots");
+        if (kapa2 == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    KapaClearPlots (kapa2);
+    KapaInitGraph (&graphdata);
+    KapaSetFont (kapa2, "courier", 14);
+
+    pmVisualLimitsFromVectors (&graphdata, radius, flux);
+    KapaSetLimits (kapa2, &graphdata);
+
+    KapaBox (kapa2, &graphdata);
+    KapaSendLabel (kapa2, "radius", KAPA_LABEL_XM);
+    KapaSendLabel (kapa2, "flux", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.style = 2;
+    graphdata.ptype = 0;
+    graphdata.size = 1.0;
+    KapaPrepPlot (kapa2, radius->n, &graphdata);
+    KapaPlotVector (kapa2, radius->n, radius->data.F32, "x");
+    KapaPlotVector (kapa2, radius->n, flux->data.F32, "y");
+
+    // pause and wait for user input:
+    // continue, save (provide name), ??
+    char key[10];
+    fprintf (stdout, "[c]ontinue? ");
+    if (!fgets(key, 8, stdin)) {
+        psWarning("Unable to read option");
+    }
+    return true;
+}
+
+bool psphotPetrosianVisualProfileRadii (psVector *radius, psVector *flux, psVector *radiusBin, psVector *fluxBin, float peakFlux, float RadiusRef) {
+
+    float FluxRef = 500.0;
+
+    Graphdata graphdata;
+
+    // return true;
+    if (!pmVisualIsVisual()) return true;
+
+    if (kapa == -1) {
+        kapa = KapaOpenNamedSocket ("kapa", "psphot:plots");
+        if (kapa == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    KapaClearPlots (kapa);
+    KapaInitGraph (&graphdata);
+    KapaSetFont (kapa, "courier", 14);
+
+    graphdata.ymax = +1.05*peakFlux;
+    graphdata.ymin = -0.05*peakFlux;
+    pmVisualLimitsFromVectors (&graphdata, radius, NULL);
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "radius", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "flux", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.style = 2;
+    graphdata.ptype = 0;
+    graphdata.size = 1.0;
+    KapaPrepPlot (kapa, radius->n, &graphdata);
+    KapaPlotVector (kapa, radius->n, radius->data.F32, "x");
+    KapaPlotVector (kapa, radius->n, flux->data.F32, "y");
+
+    // do this with log-r, log-flux?
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.style = 2;
+    graphdata.ptype = 2;
+    graphdata.size = 2.0;
+    KapaPrepPlot   (kapa, radiusBin->n, &graphdata);
+    KapaPlotVector (kapa, radiusBin->n, radiusBin->data.F32, "x");
+    KapaPlotVector (kapa, radiusBin->n, fluxBin->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.style = 2;
+    graphdata.ptype = 7;
+    graphdata.size = 3.0;
+    KapaPrepPlot (kapa, 1, &graphdata);
+    KapaPlotVector (kapa, 1, &RadiusRef, "x");
+    KapaPlotVector (kapa, 1, &FluxRef, "y");
+
+    fprintf (stderr, "radius: %f\n", RadiusRef);
+
+    // pause and wait for user input:
+    // continue, save (provide name), ??
+    char key[10];
+    fprintf (stdout, "[c]ontinue? ");
+    if (!fgets(key, 8, stdin)) {
+        psWarning("Unable to read option");
+    }
+    return true;
+}
+
+bool psphotPetrosianVisualStats (psVector *radBin, psVector *fluxBin, 
+				 psVector *refRadius, psVector *meanSB, 
+				 psVector *petRatio, psVector *petRatioErr,
+				 psVector *fluxSum, 
+				 float petRadius, float ratioForRadius,
+				 float petFlux, float radiusForFlux)
+{
+    Graphdata graphdata;
+    KapaSection section;
+
+    if (!pmVisualIsVisual()) return true;
+
+    if (kapa == -1) {
+        kapa = KapaOpenNamedSocket ("kapa", "psphot:plots");
+        if (kapa == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    KapaClearPlots (kapa);
+    KapaInitGraph (&graphdata);
+    KapaSetFont (kapa, "courier", 14);
+
+    // radius vs flux
+    // radius vs mean SB
+    // radius vs petRatio
+
+    // *** section 1: radius vs mean SB
+    section.dx = 1.00;
+    section.dy = 0.33;
+    section.x  = 0.00;
+    section.y  = 0.00;
+    section.name = psStringCopy ("meanSB");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    pmVisualLimitsFromVectors (&graphdata, radBin, fluxBin);
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "radius", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "mean SB", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.style = 2;
+    graphdata.ptype = 0;
+    graphdata.size = 1.0;
+    KapaPrepPlot (kapa, radBin->n, &graphdata);
+    KapaPlotVector (kapa, radBin->n, radBin->data.F32, "x");
+    KapaPlotVector (kapa, radBin->n, fluxBin->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.style = 2;
+    graphdata.ptype = 1;
+    graphdata.size = 2.0;
+    KapaPrepPlot (kapa, refRadius->n, &graphdata);
+    KapaPlotVector (kapa, refRadius->n, refRadius->data.F32, "x");
+    KapaPlotVector (kapa, refRadius->n, meanSB->data.F32, "y");
+
+    // *** section 2: radius vs petrosian ratio
+    section.dx = 1.00;
+    section.dy = 0.33;
+    section.x  = 0.00;
+    section.y  = 0.33;
+    section.name = psStringCopy ("ratio");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ymax = +1.05;
+    graphdata.ymin = -0.05;
+    pmVisualLimitsFromVectors (&graphdata, radBin, NULL);
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "radius", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "ratio", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.style = 2;
+    graphdata.ptype = 0;
+    graphdata.size = 1.0;
+    graphdata.etype = 0x01;
+    KapaPrepPlot (kapa, refRadius->n, &graphdata);
+    KapaPlotVector (kapa, refRadius->n, refRadius->data.F32, "x");
+    KapaPlotVector (kapa, refRadius->n, petRatio->data.F32, "y");
+    KapaPlotVector (kapa, refRadius->n, petRatioErr->data.F32, "dym");
+    KapaPlotVector (kapa, refRadius->n, petRatioErr->data.F32, "dyp");
+    graphdata.etype = 0;
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.style = 2;
+    graphdata.ptype = 2;
+    graphdata.size = 2.0;
+    KapaPrepPlot   (kapa, 1, &graphdata);
+    KapaPlotVector (kapa, 1, &petRadius, "x");
+    KapaPlotVector (kapa, 1, &ratioForRadius, "y");
+
+    // *** section 3: radius vs integrated flux
+    section.dx = 1.00;
+    section.dy = 0.33;
+    section.x  = 0.00;
+    section.y  = 0.66;
+    section.name = psStringCopy ("flux");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    pmVisualLimitsFromVectors (&graphdata, radBin, fluxSum);
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "radius", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "integrated flux", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.style = 2;
+    graphdata.ptype = 0;
+    graphdata.size = 1.0;
+    KapaPrepPlot   (kapa, refRadius->n, &graphdata);
+    KapaPlotVector (kapa, refRadius->n, refRadius->data.F32, "x");
+    KapaPlotVector (kapa, refRadius->n, fluxSum->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.style = 2;
+    graphdata.size = 2.0;
+    KapaPrepPlot   (kapa, 1, &graphdata);
+    KapaPlotVector (kapa, 1, &radiusForFlux, "x");
+    KapaPlotVector (kapa, 1, &petFlux, "y");
+
+    // pause and wait for user input:
+    // continue, save (provide name), ??
+    char key[10];
+    fprintf (stdout, "[c]ontinue? ");
+    if (!fgets(key, 8, stdin)) {
+        psWarning("Unable to read option");
+    }
+    return true;
+}
+
+bool psphotPetrosianVisualEllipticalContour (pmPetrosian *petrosian) {
+
+    Graphdata graphdata;
+
+    if (!pmVisualIsVisual()) return true;
+
+    if (kapa == -1) {
+        kapa = KapaOpenNamedSocket ("kapa", "psphot:plots");
+        if (kapa == -1) {
+            fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+            pmVisualSetVisual(false);
+            return false;
+        }
+    }
+
+    KapaClearPlots (kapa);
+    KapaInitGraph (&graphdata);
+    KapaSetFont (kapa, "courier", 14);
+
+    psVector *theta = petrosian->theta;
+    psVector *radius = petrosian->isophotalRadii;
+
+    // find Rmin and Rmax for the initial guess
+    float Rmin = radius->data.F32[0];
+    float Rmax = radius->data.F32[0];
+
+    psVector *Rx = psVectorAlloc(radius->n, PS_TYPE_F32);
+    psVector *Ry = psVectorAlloc(radius->n, PS_TYPE_F32);
+
+    for (int i = 0; i < theta->n; i++) {
+	Rx->data.F32[i] = radius->data.F32[i]*cos(theta->data.F32[i]);
+	Ry->data.F32[i] = radius->data.F32[i]*sin(theta->data.F32[i]);
+
+	// check the radius range
+	Rmin = MIN (Rmin, radius->data.F32[i]);
+	Rmax = MAX (Rmax, radius->data.F32[i]);
+    }	
+
+    psVector *rx = psVectorAlloc(361, PS_TYPE_F32);
+    psVector *ry = psVectorAlloc(361, PS_TYPE_F32);
+
+    float epsilon = petrosian->axes.minor / petrosian->axes.major;
+
+    for (int i = 0; i < 361; i++) {
+
+	float alpha = PS_RAD_DEG * i;
+
+	float cs_alpha = cos(alpha);
+	float sn_alpha = sin(alpha);
+
+	float cs_phi = cos(alpha - petrosian->axes.theta);
+	float sn_phi = sin(alpha - petrosian->axes.theta);
+
+	float r = 1.0 / sqrt(SQ(sn_phi) + SQ(epsilon*cs_phi));
+
+	// generate the model fit here
+	rx->data.F32[i] = petrosian->axes.minor * cs_alpha * r;
+	ry->data.F32[i] = petrosian->axes.minor * sn_alpha * r;
+    }	
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = -1.1*Rmax;
+    graphdata.ymin = -1.1*Rmax;
+    graphdata.xmax = +1.1*Rmax;
+    graphdata.ymax = +1.1*Rmax;
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "R_x", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "R_y", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.style = 2;
+    graphdata.ptype = 2;
+    graphdata.size = 1.0;
+    KapaPrepPlot (kapa, Rx->n, &graphdata);
+    KapaPlotVector (kapa, Rx->n, Rx->data.F32, "x");
+    KapaPlotVector (kapa, Rx->n, Ry->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.style = 0;
+    graphdata.ptype = 0;
+    graphdata.size = 1.0;
+    KapaPrepPlot (kapa, rx->n, &graphdata);
+    KapaPlotVector (kapa, rx->n, rx->data.F32, "x");
+    KapaPlotVector (kapa, rx->n, ry->data.F32, "y");
+
+    // pause and wait for user input:
+    // continue, save (provide name), ??
+    char key[10];
+    fprintf (stdout, "[c]ontinue? ");
+    if (!fgets(key, 8, stdin)) {
+        psWarning("Unable to read option");
+    }
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialPlot.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialPlot.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialPlot.c	(revision 27622)
@@ -0,0 +1,118 @@
+# include "psphotInternal.h"
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+
+# include <kapa.h>
+
+static int nCount = 0;
+
+bool psphotRadialPlot (int *kapa, const char *filename, pmSource *source) {
+
+    Graphdata graphdata;
+
+    // only plot 50 stars for now...
+    if (nCount > 00) {
+	if (*kapa != 0) {
+	    KiiClose (*kapa);
+	    *kapa = 0;
+	}
+	return true;
+    }
+
+    // XXX get the 'showWindow' option from the recipes somewhere
+    // XXX 'showWindow = false' is broken
+    if (*kapa == 0) {
+	*kapa = pmKapaOpen (false);
+	KapaResize (*kapa, 500, 500);
+	unlink (filename);
+    }
+    if (*kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (*kapa);
+
+    // examine sources to set data range
+    graphdata.xmin =  -0.05;
+    graphdata.xmax = +30.05;
+    graphdata.ymin = -0.05;
+    graphdata.ymax = +5.05;
+    KapaSetLimits (*kapa, &graphdata);
+  
+    KapaSetFont (*kapa, "helvetica", 14);
+    KapaBox (*kapa, &graphdata);
+    KapaSendLabel (*kapa, "radius (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (*kapa, "log flux (counts)", KAPA_LABEL_YM);
+	       
+    int nPts = source->pixels->numRows * source->pixels->numCols;
+    psVector *rg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *rb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+
+    int ng = 0;
+    int nb = 0;
+    float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS] - source->pixels->col0;
+    float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS] - source->pixels->row0;
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	for (int ix = 0; ix < source->pixels->numCols; ix++) {
+	    if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix]) {
+		rb->data.F32[nb] = hypot (ix - Xo, iy - Yo) ;
+		fb->data.F32[nb] = log10(source->pixels->data.F32[iy][ix]);
+		nb++;
+	    } else {
+		rg->data.F32[ng] = hypot (ix - Xo, iy - Yo) ;
+		fg->data.F32[ng] = log10(source->pixels->data.F32[iy][ix]);
+		ng++;
+	    }
+	}
+    }
+  
+    // set the plot range here based on lflux, radius
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (*kapa, ng, &graphdata);
+    KapaPlotVector (*kapa, ng, rg->data.F32, "x");
+    KapaPlotVector (*kapa, ng, fg->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (*kapa, nb, &graphdata);
+    KapaPlotVector (*kapa, nb, rb->data.F32, "x");
+    KapaPlotVector (*kapa, nb, fb->data.F32, "y");
+  
+    psLogMsg ("psphot", 3, "saving plot to %s", filename);
+
+    char pagename[16];
+    sprintf (pagename, "%02d", nCount);
+    if (nCount == 0) {
+	KiiPS (*kapa, filename, false, KAPA_PS_NEWPLOT, pagename);
+    } else {
+	KiiPS (*kapa, filename, false, KAPA_PS_NEWPAGE, pagename);
+    }
+
+    psFree (rg);
+    psFree (fg);
+    psFree (rb);
+    psFree (fb);
+
+    nCount ++;
+    return true;
+}
+
+# else
+
+bool psphotRadialPlot (int *kapa, const char *filename, pmSource *source) {
+    psLogMsg ("psphot", 3, "skipping source radial plots");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialProfile.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialProfile.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialProfile.c	(revision 27622)
@@ -0,0 +1,50 @@
+# include "psphotInternal.h"
+
+bool psphotRadialProfile (pmSource *source, psMetadata *recipe, float skynoise, psImageMaskType maskVal) {
+
+    // allocate pmSourceExtendedParameters, if not already defined
+    if (!source->extpars) {
+        source->extpars = pmSourceExtendedParsAlloc ();
+    }
+
+    if (!source->extpars->profile) {
+        source->extpars->profile = pmSourceRadialProfileAlloc ();
+    }
+
+    // XXX these need to go into recipe values
+    int Nsec = 24;
+    float Rmax = 200;
+    float fluxMin = 0.0;
+    float fluxMax = source->peak->flux;
+
+    // generate a series of radial profiles at Nsec evenly spaced angles.  the profile flux
+    // is measured by interpolation for small radii; for large radii, the pixels in a box
+    // are averaged to increase the S/N
+    if (!psphotRadialProfilesByAngles (source, Nsec, Rmax)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to measure radial profile for petrosian");
+	return false;
+    }
+
+    // use the radial profiles to determine the radius of a given isophote.  this isophote
+    // is used to determine the elliptical shape of the object, so it has a relatively high
+    // value (nominally 50% of the peak)
+    if (!psphotRadiiFromProfiles (source, fluxMin, fluxMax)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to measure isophotal radii from profiles");
+	return false;
+    }
+
+    // convert the isophotal radius vs angle measurements to an elliptical contour
+    if (!psphotEllipticalContour (source)) {
+	psLogMsg ("psphot", 3, "failed to measure elliptical contour");
+	return false;
+    }
+  
+    // generate a single, normalized radial profile following the elliptical contours.
+    // the radius is normalized by the axis ratio so that on the major axis, 1 pixel = 1 pixel
+    if (!psphotEllipticalProfile (source)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to generate elliptical profile");
+	return false;
+    }
+  
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialProfileByAngles.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialProfileByAngles.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadialProfileByAngles.c	(revision 27622)
@@ -0,0 +1,239 @@
+# include "psphotInternal.h"
+
+// Given a source at (x,y), generate a collection of radial profiles at even angular separations
+
+// These functions are used to calculate the stats in a rectangle at arbitrary orientation.
+// XXX Move these elsewhere (psLib?)
+float psphotMeanSectorValue (psImage *image, float x, float y, float dL, float dW, float theta);
+psVector *psphotBoxValues (psImage *image, float x0, float y0, float dL, float dW, float theta);
+psVector *psphotLineValues (psImage *image, double x1, double y1, double x2, double y2, int dW);
+psVector *psphotLineValuesBresen (psImage *image, int X1, int Y1, int X2, int Y2, int dW, int swapcoords);
+
+bool psphotRadialProfilesByAngles (pmSource *source, int Nsec, float Rmax) {
+
+    // we want to have an even number of sectors so we can do 180 deg symmetrizing
+    Nsec = (Nsec % 2) ? Nsec + 1 : Nsec;
+    float dtheta = 2.0*M_PI / Nsec;
+
+    pmSourceRadialProfile *profile = source->extpars->profile;
+    psFree(profile->radii);
+    psFree(profile->fluxes);
+    psFree(profile->theta);
+
+    profile->radii = psArrayAllocEmpty(Nsec);
+    profile->fluxes = psArrayAllocEmpty(Nsec);
+    profile->theta = psVectorAllocEmpty(Nsec, PS_TYPE_F32);
+
+
+    for (int i = 0; i < Nsec; i++) {
+
+	float theta = i*dtheta;
+
+	psVector *radius = psVectorAllocEmpty(Rmax, PS_TYPE_F32);
+	psVector *flux   = psVectorAllocEmpty(Rmax, PS_TYPE_F32);
+
+	// Start at Xo,Yo and find the x,y locations for r_i, theta where r_i initially
+	// increments by 1 pixel.  At large radii (r*dtheta > 2) use stats in a box rather than
+	// sub-pixel interpolation
+
+	int dR = 1.0;
+	for (float r = 0; r < Rmax; r += dR) {
+
+	    float Xo = source->peak->xf;
+	    float Yo = source->peak->yf;
+
+	    // Xo,Yo are referenced to pixels with bounds i+0.0, i+1.0
+	    float x = r * cos (theta) + Xo;
+	    float y = r * sin (theta) + Yo;
+	    dR = 2*(int)(0.5*r*sin(dtheta)) + 1;
+
+	    if (x < 0) goto badvalue;
+	    if (y < 0) goto badvalue;
+	    if (x >= source->pixels->parent->numCols) goto badvalue;
+	    if (y >= source->pixels->parent->numRows) goto badvalue;
+
+	    float value = NAN;
+	    if (dR < 2) {
+		// value is NAN if we run off the image
+		// 0.5 PIX: this function takes pixel coords; source peak is in pixel coords
+		value = psImageInterpolatePixelBilinear(x, y, source->pixels);
+	    } else {
+		// 0.5 PIX: this function takes pixel coords; source peak is in pixel coords
+		value = psphotMeanSectorValue(source->pixels, x, y, dR, dR, theta);
+	    }
+
+	    // keep the all values (even NAN) so all vectors are matched in length
+	    psVectorAppend (radius, r);
+	    psVectorAppend (flux, value);
+	    continue;
+	    
+	badvalue:
+	    psVectorAppend (radius, r);
+	    psVectorAppend (flux, NAN);
+	}
+
+	psArrayAdd (profile->radii, 100, radius);
+	psArrayAdd (profile->fluxes, 100, flux);
+	psVectorAppend (profile->theta, theta);
+
+	// psphotPetrosianVisualProfileByAngle (radius, flux);
+
+	psFree(radius);
+	psFree(flux);
+    }
+
+    for (int i = 0; i < Nsec / 2; i++) {
+
+	psVector *r1 = profile->radii->data[i];
+	psVector *r2 = profile->radii->data[i+Nsec/2];
+
+	psVector *f1 = profile->fluxes->data[i];
+	psVector *f2 = profile->fluxes->data[i+Nsec/2];
+
+	psAssert (r1->n == r2->n, "mis-matched vectors");
+	psAssert (f1->n == f2->n, "mis-matched vectors");
+
+	// we have a pair of vectors i, i+Nsec/2; replace them with the finite minimum of the pair
+	for (int j = 0; j < r1->n; j++) {
+	    
+	    float flux;
+
+	    if (!isfinite(f1->data.F32[j]) && !isfinite(f2->data.F32[j])) {
+		flux = NAN;
+		goto setflux;
+	    }
+
+	    if (!isfinite(f1->data.F32[j])) {
+		flux = f2->data.F32[j];
+		goto setflux;
+	    }
+	    if (!isfinite(f2->data.F32[j])) {
+		flux = f1->data.F32[j];
+		goto setflux;
+	    }
+
+	    flux = PS_MIN(f1->data.F32[j], f2->data.F32[j]);
+
+	setflux:
+	    f1->data.F32[j] = flux;
+	    f2->data.F32[j] = flux;
+	}
+    }    
+    return true;
+}
+
+float psphotMeanSectorValue (psImage *image, float x, float y, float dL, float dW, float theta) {
+
+    psVector *values = psphotBoxValues (image, x, y, dL, dW, theta);
+    if (!values) goto escape;
+    if (!values->n) goto escape;
+    
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, values, NULL, NULL, 0);
+
+    float value = stats->sampleMedian;
+
+    psFree (stats);
+    psFree (values);
+    
+    return value;
+
+escape:
+    psFree(values);
+    return NAN;    
+}
+
+psVector *psphotBoxValues (psImage *image, float x0, float y0, float dL, float dW, float theta) {
+
+    // extract pixels from a series of lines (from -0.5*dW to +0.5*dW) of length dL, 
+    // centered on x0, y0 in parent pixel coordinates (not pixel indicies)
+
+    float xs = x0 - image->col0 - 0.5*dL*cos(theta);
+    float ys = y0 - image->row0 - 0.5*dL*sin(theta);
+
+    float xe = xs + 0.5*dL*cos(theta);
+    float ye = ys + 0.5*dL*sin(theta);
+
+    psVector *values = psphotLineValues (image, xs, ys, xe, ye, (int) dW);
+    return values;
+}
+
+/**
+ * identify the quadrant and draw the correct line
+ */
+psVector *psphotLineValues (psImage *image, double x1, double y1, double x2, double y2, int dW) {
+
+  int FlipDirect, FlipCoords;
+  int X1, Y1, X2, Y2, dX, dY;
+
+  /* rather than draw the line from float positions, we find the closest
+     integer end-points and draw the line between those pixels */
+
+  X1 = ROUND(x1);
+  Y1 = ROUND(y1);
+  X2 = ROUND(x2);
+  Y2 = ROUND(y2);
+
+  dX = X2 - X1;
+  dY = Y2 - Y1;
+
+  FlipCoords = (abs(dX) < abs(dY));
+  FlipDirect = FlipCoords ? (y1 > y2) : (x1 > x2);
+
+  psVector *values = NULL;
+  if (!FlipDirect && !FlipCoords) values = psphotLineValuesBresen (image, X1, Y1, X2, Y2, dW, FALSE);
+  if ( FlipDirect && !FlipCoords) values = psphotLineValuesBresen (image, X2, Y2, X1, Y1, dW, FALSE);
+  if (!FlipDirect &&  FlipCoords) values = psphotLineValuesBresen (image, Y1, X1, Y2, X2, dW, TRUE);
+  if ( FlipDirect &&  FlipCoords) values = psphotLineValuesBresen (image, Y2, X2, Y1, X1, dW, TRUE);
+
+  return values;
+}
+
+/**
+ * use the Bresenham line drawing technique
+ * integer-only Bresenham line-draw version which is fast
+ */
+psVector *psphotLineValuesBresen (psImage *image, int X1, int Y1, int X2, int Y2, int dW, int swapcoords) {
+
+    int X, Y, dX, dY;
+    int e, e2;
+
+    psVector *values = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    dX = X2 - X1;
+    dY = Y2 - Y1;
+
+    Y = Y1;
+    e = 0;
+    for (X = X1; X <= X2; X++) {
+        if (X > 0) {
+            if (swapcoords) {
+                if (X >= image->numRows) continue;
+                for (int y = Y - dW; y <= Y + dW; y++) {
+                    if (y < 0) continue;
+                    if (y >= image->numCols) continue;
+                    psVectorAppend(values, image->data.F32[X][y]);
+                }
+            } else {
+                if (X >= image->numCols) continue;
+                for (int y = Y - dW; y <= Y + dW; y++) {
+                    if (y < 0) continue;
+                    if (y >= image->numRows) continue;
+                    psVectorAppend(values, image->data.F32[y][X]);
+                }
+            }
+        }
+        e += dY;
+        e2 = 2 * e;
+        if (e2 > dX) {
+            Y++;
+            e -= dX;
+        }
+        if (e2 < -dX) {
+            Y--;
+            e += dX;
+        }
+    }
+    return values;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadiiFromProfiles.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadiiFromProfiles.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadiiFromProfiles.c	(revision 27622)
@@ -0,0 +1,153 @@
+# include "psphotInternal.h"
+
+// Given the Radial Profiles (radii, fluxes) determine the radius for each profile at the desired isophote
+
+bool psphotRadiiFromProfiles (pmSource *source, float fluxMin, float fluxMax) {
+
+    pmSourceRadialProfile *profile = source->extpars->profile;
+
+    psFree(profile->isophotalRadii);
+    profile->isophotalRadii = psVectorAlloc(profile->theta->n, PS_TYPE_F32);
+
+    for (int i = 0; i < profile->theta->n; i++) {
+	psVector *radii = profile->radii->data[i];
+	psVector *fluxes = profile->fluxes->data[i];
+	float radius = psphotRadiusFromProfile (source, radii, fluxes, fluxMin, fluxMax);
+
+	// psphotPetrosianVisualProfileByAngle (radii, fluxes, radius);
+
+	// warn on NAN?
+	profile->isophotalRadii->data.F32[i] = radius;
+    }
+    return true;
+}
+
+float psphotRadiusFromProfile (pmSource *source, psVector *radius, psVector *flux, float fluxMin, float fluxMax) {
+
+    // 'flux' is a noisy sample of the galaxy radial profile at points 'radius'
+    // rebin flux into samples defined by the isophote Fo = 0.5*(fluxMax + fluxMin).  the noisy
+    // sample is cleaned by rebinning to a well-matched radial binning
+
+    // base selections on fluxes defined by the flux range dF
+    float fluxRange = fluxMax - fluxMin;
+
+    // examine data in the two ranges Fm - Fo and Fo - Fp to define the bin size
+    // XXX reconsider the fractional isophote value
+    float Fm = fluxMin + 0.10*fluxRange;
+    float Fo = fluxMin + 0.25*fluxRange;
+    float Fp = fluxMin + 0.50*fluxRange;
+    int Rbin = 1;
+      
+    // find the median radius of the points in the flux range Fm - Fp:
+    { 
+	// storage vector for stats
+	psVector *values = psVectorAllocEmpty (flux->n, PS_TYPE_F32);
+	psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+
+	for (int i = 0; i < flux->n; i++) {
+	    if (!isfinite(flux->data.F32[i])) continue;
+	    if (flux->data.F32[i] < Fm) continue;
+	    if (flux->data.F32[i] > Fp) continue;
+	    
+	    psVectorAppend (values, radius->data.F32[i]);
+	}
+	if (values->n > 1) {
+	    psVectorStats (stats, values, NULL, NULL, 0);
+
+	    // if we have a valid range, rebin with bin size 1/2 of median radius
+	    if (isfinite(stats->sampleMedian)) {
+		Rbin = MAX(1, 0.5*stats->sampleMedian);
+	    }
+	}
+	psFree (values);
+	psFree (stats);
+    }
+    Rbin = 3;
+
+    psVector *fluxBinned = NULL;
+    psVector *radiusBinned = NULL;
+
+    // do not bother rebinning if the bin size is only 2 or less
+    if (Rbin <= 2) {
+	fluxBinned = psMemIncrRefCounter (flux);
+	radiusBinned = psMemIncrRefCounter (radius);
+    } else {
+	// storage vector for stats
+	psVector *values = psVectorAllocEmpty (flux->n, PS_TYPE_F32);
+	psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+  
+	// rebinned vectors
+	fluxBinned = psVectorAllocEmpty (flux->n, PS_TYPE_F32);
+	radiusBinned = psVectorAllocEmpty (flux->n, PS_TYPE_F32);
+
+	// sort the flux by the radius
+	pmSourceRadialProfileSortPair (radius, flux);
+
+	int nOut = 0;
+	radiusBinned->data.F32[nOut] = (nOut + 0.5)*Rbin;
+	float Rmin = radiusBinned->data.F32[nOut] - 0.5*Rbin;
+	float Rmax = radiusBinned->data.F32[nOut] + 0.5*Rbin;
+
+	for (int i = 0; i < flux->n; i++) {
+	    if (radius->data.F32[i] < Rmin) {
+		// XXX not sure how we can hit this, if there is full coverage of radiusBinned
+		continue;
+	    }
+	    if (radius->data.F32[i] > Rmax) {
+		// calculate the value for the nOut bin
+		// XXX need to fix this as well psStats (stats, values);
+		float value;
+		if (values->n > 0) {
+		    psVectorStats (stats, values, NULL, NULL, 0);
+		    value = stats->sampleMedian;
+		} else {
+		    value = NAN;
+		}
+		fluxBinned->data.F32[nOut] = value;
+		nOut ++;
+		radiusBinned->data.F32[nOut] = (nOut + 0.5)*Rbin;
+		Rmin = radiusBinned->data.F32[nOut] - 0.5*Rbin;
+		Rmax = radiusBinned->data.F32[nOut] + 0.5*Rbin;
+		values->n = 0;
+		psStatsInit(stats);
+	    }
+	    if (!isfinite(flux->data.F32[i])) continue;
+	    psVectorAppend (values, flux->data.F32[i]);
+	}
+	fluxBinned->n = nOut;
+	radiusBinned->n = nOut;
+	psFree (values);
+	psFree(stats);
+    }
+
+    float Ro = NAN;
+    bool above = true;
+    for (int i = 0; i < fluxBinned->n; i++) {
+
+	if (!isfinite(fluxBinned->data.F32[i])) continue;
+
+	// find the largest radius that matches the flux transition
+	if (above && (fluxBinned->data.F32[i] < Fo)) {
+	    // XXX is there a macro in psLib that does this interpolation?
+	    if (i == 0) { 
+		psTrace ("psphot", 4, "bogus radial profile for source at %f, %f, skipping", source->peak->xf, source->peak->yf);
+		psFree (fluxBinned);
+		psFree (radiusBinned);
+		return NAN;
+	    } 
+	    Ro = radiusBinned->data.F32[i-1] + (radiusBinned->data.F32[i] - radiusBinned->data.F32[i-1]) * (Fo - fluxBinned->data.F32[i-1]) / (fluxBinned->data.F32[i] - fluxBinned->data.F32[i-1]);
+	    above = FALSE;
+	}
+  
+	if (!above && (fluxBinned->data.F32[i] >= Fo)) {
+	    above = TRUE;
+	}
+    }
+
+    // show the results
+    // psphotPetrosianVisualProfileRadii (radius, flux, radiusBinned, fluxBinned, Ro);
+
+    psFree(fluxBinned);
+    psFree(radiusBinned);
+    return Ro;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadiusChecks.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadiusChecks.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotRadiusChecks.c	(revision 27622)
@@ -0,0 +1,186 @@
+# include "psphotInternal.h"
+# define RADIUS_TYPE int
+
+static float PSF_FIT_NSIGMA;
+static float PSF_FIT_PADDING;
+static float PSF_APERTURE = 0;	// radius to use in PSF aperture mags
+static float PSF_FIT_RADIUS = 0;	// radius to use in fitting (ignored if <= 0,
+					// and a per-object radius is calculated)
+
+bool psphotInitRadiusPSF(const psMetadata *recipe, const psMetadata *analysis, const pmModelType type) {
+
+    bool status = true;
+
+    PSF_FIT_NSIGMA = psMetadataLookupF32(&status, recipe, "PSF_FIT_NSIGMA");
+    PSF_FIT_PADDING = psMetadataLookupF32(&status, recipe, "PSF_FIT_PADDING");
+
+    PSF_FIT_RADIUS =  psMetadataLookupF32(&status, analysis, "PSF_FIT_RADIUS");
+    if (!status) {
+	PSF_FIT_RADIUS = psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS");
+    }
+
+    PSF_APERTURE =  psMetadataLookupF32(&status, analysis, "PSF_APERTURE");
+    if (!status) {
+	PSF_APERTURE =  psMetadataLookupF32(&status, recipe, "PSF_APERTURE");
+    }
+
+    return true;
+}
+
+// call this function whenever you (re)-define the PSF model
+bool psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // XXX do we have a better value for the sky noise level?  not really...
+    pmMoments *moments = source->moments;
+
+    // set the fit radius based on the object flux limit and the model
+    float radiusFit = PSF_FIT_RADIUS;
+    if (radiusFit <= 0) {		// use fixed radius
+	if (moments == NULL) {
+	    radiusFit = model->modelRadius(model->params, PSF_FIT_NSIGMA*moments->dSky);
+	} else {
+	    radiusFit = model->modelRadius(model->params, 1.0);
+	}
+	model->fitRadius = (RADIUS_TYPE)(radiusFit + PSF_FIT_PADDING);
+    } else {
+	model->fitRadius = radiusFit;
+    }
+    if (isnan(model->fitRadius)) psAbort("error in radius");
+	
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	model->fitRadius *= 2;
+    }
+
+    // radius used to measure aperture photometry
+    source->apRadius = PSF_APERTURE;
+
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius, "OR", markVal);
+    return status;
+}
+
+bool psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal, float dR) {
+
+    psF32 *PAR = model->params->data.F32;
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    float radiusFit = PSF_FIT_RADIUS;
+    if (radiusFit <= 0) {		// use fixed radius
+	if (moments == NULL) {
+	    radiusFit = model->modelRadius(model->params, PSF_FIT_NSIGMA*moments->dSky);
+	} else {
+	    radiusFit = model->modelRadius(model->params, 1.0);
+	}
+	model->fitRadius = (RADIUS_TYPE)(radiusFit + PSF_FIT_PADDING);
+    } else {
+	model->fitRadius = radiusFit;
+    }
+    if (isnan(model->fitRadius)) psAbort("error in radius");
+
+    // above sets a radius for a single star, bump by blend separation
+    model->fitRadius += dR;
+
+    if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+	model->fitRadius *= 2;
+    }
+
+    // radius used to measure aperture photometry
+    source->apRadius = PSF_APERTURE;
+
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius, "OR", markVal);
+    return status;
+}
+
+static float EXT_FIT_NSIGMA;
+static float EXT_FIT_PADDING;
+static float EXT_FIT_MAX_RADIUS;
+
+bool psphotInitRadiusEXT (psMetadata *recipe, pmModelType type) {
+
+    bool status;
+
+    EXT_FIT_NSIGMA     = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA");
+    EXT_FIT_PADDING    = psMetadataLookupF32 (&status, recipe, "EXT_FIT_PADDING");
+    EXT_FIT_MAX_RADIUS = psMetadataLookupF32 (&status, recipe, "EXT_FIT_MAX_RADIUS");
+
+    return true;
+}
+
+// call this function whenever you (re)-define the EXT model
+float psphotSetRadiusEXT (pmReadout *readout, pmSource *source, psImageMaskType markVal) {
+
+    psAssert (source, "source not defined??");
+    psAssert (source->peak, "peak not defined??");
+
+    pmPeak *peak = source->peak;
+
+    // set the radius based on the footprint:
+    if (!peak->footprint) goto escape;
+    pmFootprint *footprint = peak->footprint;
+    if (!footprint->spans) goto escape;
+    if (footprint->spans->n < 1) goto escape;
+
+    // find the max radius
+    float radius = 0.0;
+    for (int j = 0; j < footprint->spans->n; j++) {
+	pmSpan *span = footprint->spans->data[j];
+
+	float dY  = span->y  - peak->yf;
+	float dX0 = span->x0 - peak->xf;
+	float dX1 = span->x1 - peak->xf;
+
+	radius = PS_MAX (radius, hypot(dY, dX0));
+	radius = PS_MAX (radius, hypot(dY, dX1));
+    }
+
+    radius += EXT_FIT_PADDING;
+    if (isnan(radius)) psAbort("error in radius");
+
+    radius = PS_MIN (radius, EXT_FIT_MAX_RADIUS);
+
+    // redefine the pixels if needed
+    pmSourceRedefinePixels (source, readout, peak->xf, peak->yf, radius);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, peak->xf, peak->yf, radius, "OR", markVal);
+    return radius;
+
+escape:
+    return NAN;
+    // bool result = psphotCheckRadiusEXT (readout, source, model, markVal);
+    // return result;
+}
+
+// alternative EXT radius based on model guess (for use without footprints)
+bool psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model, psImageMaskType markVal) {
+
+    psAbort ("do not use this function");
+
+    psF32 *PAR = model->params->data.F32;
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    float rawRadius = model->modelRadius (model->params, EXT_FIT_NSIGMA*moments->dSky);
+
+    model->fitRadius = rawRadius + EXT_FIT_PADDING;
+    if (isnan(model->fitRadius)) psAbort("error in radius");
+
+    // redefine the pixels if needed
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->fitRadius, "OR", markVal);
+    return status;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadout.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadout.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadout.c	(revision 27622)
@@ -0,0 +1,226 @@
+# include "psphotInternal.h"
+
+// this should be called by every program that links against libpsphot
+bool psphotInit (void) {
+    psphotErrorRegister();              // register our error codes/messages
+    psphotModelClassInit ();            // load implementation-specific models
+    psphotSetThreads ();
+    return true;
+}
+
+bool psphotReadout(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  dtime is the elapsed time used jointly
+    // by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX);
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    psAssert (breakPt, "configuration error: set BREAK_POINT");
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (config, view)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view);
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are determined and saved on
+    // readout->analysis XXX this function currently only works with a single PSPHOT.INPUT
+    if (!psphotLoadPSF (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "error loading psf model");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // find the detections (by peak and/or footprint) in the image.
+    if (!psphotFindDetections (config, view, true)) { // pass 1
+        // this only happens if we had an error in psphotFindDetections
+        psError (PSPHOT_ERR_UNKNOWN, false, "failure in peak analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // construct sources and measure basic stats (saved on detections->newSources)
+    if (!psphotSourceStats (config, view, true)) { // pass 1
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // find blended neighbors of very saturated stars (detections->newSources)
+    if (!psphotDeblendSatstars (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed on satstar deblend analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // mark blended peaks PS_SOURCE_BLEND (detections->newSources)
+    if (!psphotBasicDeblend (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed on deblend analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // classify sources based on moments, brightness.  if a PSF model has been loaded, the PSF
+    // clump defined for it is used not measured (detections->newSources)
+    if (!psphotRoughClass (config, view)) { // pass 1
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough classifications");
+        return psphotReadoutCleanup (config, view);
+    }
+    // if we were not supplied a PSF model, determine the IQ stats here (detections->newSources)
+    if (!psphotImageQuality (config, view)) { // pass 1
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to measure image quality");
+        return psphotReadoutCleanup(config, view);
+    }
+    if (!strcasecmp (breakPt, "MOMENTS")) {
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // use bright stellar objects to measure PSF if we were supplied a PSF for any input file,
+    // this step is skipped
+    if (!psphotChoosePSF (config, view)) { // pass 1
+        psLogMsg ("psphot", 3, "failure to construct a psf model");
+        return psphotReadoutCleanup (config, view);
+    }
+    if (!strcasecmp (breakPt, "PSFMODEL")) {
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // include externally-supplied sources
+    // XXX fix this in the new multi-input context
+    // psphotLoadExtSources (config, view); // pass 1
+
+    // construct an initial model for each object, set the radius to fitRadius, set circular
+    // fit mask (detections->newSources)
+    psphotGuessModels (config, view); // pass 1
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view);
+
+    // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
+    psphotFitSourcesLinear (config, view, false); // pass 1 (detections->allSources)
+
+    // identify CRs and extended sources (only unmeasured sources are measured)
+    psphotSourceSize (config, view, true); // pass 1 (detections->allSources)
+    if (!strcasecmp (breakPt, "ENSEMBLE")) {
+        goto finish;
+    }
+
+    // non-linear PSF and EXT fit to brighter sources
+    // replace model flux, adjust mask as needed, fit, subtract the models (full stamp)
+    psphotBlendFit (config, view); // pass 1 (detections->allSources)
+
+    // replace all sources
+    psphotReplaceAllSources (config, view); // pass 1 (detections->allSources)
+
+    // linear fit to include all sources (subtract again)
+    // NOTE : apply to ALL sources (extended + psf)
+    psphotFitSourcesLinear (config, view, true); // pass 2 (detections->allSources)
+
+    // if we only do one pass, skip to extended source analysis
+    if (!strcasecmp (breakPt, "PASS1")) goto pass1finish;
+
+    // NOTE: possibly re-measure background model here with objects subtracted / or masked
+
+    // add noise for subtracted objects
+    psphotAddNoise (config, view); // pass 1 (detections->allSources)
+
+    // find fainter sources
+    // NOTE: finds new peaks and new footprints, OLD and FULL set are saved on detections
+    psphotFindDetections (config, view, false); // pass 2 (detections->peaks, detections->footprints)
+
+    // remove noise for subtracted objects (ie, return to normal noise level)
+    // NOTE: this needs to operate only on the OLD sources
+    psphotSubNoise (config, view); // pass 1 (detections->allSources)
+
+    // define new sources based on only the new peaks
+    // NOTE: new sources are saved on detections->newSources
+    psphotSourceStats (config, view, false); // pass 2 (detections->newSources)
+
+    // set source type
+    // NOTE: apply only to detections->newSources
+    if (!psphotRoughClass (config, view)) { // pass 2 (detections->newSources)
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // create full input models, set the radius to fitRadius, set circular fit mask
+    // NOTE: apply only to detections->newSources
+    psphotGuessModels (config, view); // pass 2 (detections->newSources)
+
+    // replace all sources so fit below applies to all at once
+    // NOTE: apply only to OLD sources (which have been subtracted)
+    psphotReplaceAllSources (config, view); // pass 2
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    // XXX check on free of sources...
+    psphotMergeSources (config, view); // (detections->newSources + detections->allSources -> detections->allSources)
+
+    // NOTE: apply to ALL sources
+    psphotFitSourcesLinear (config, view, true); // pass 3 (detections->allSources)
+
+pass1finish:
+
+    // measure source size for the remaining sources
+    // NOTE: applies only to NEW (unmeasured) sources
+    psphotSourceSize (config, view, false); // pass 2 (detections->allSources)
+
+    psphotExtendedSourceAnalysis (config, view); // pass 1 (detections->allSources)
+    psphotExtendedSourceFits (config, view); // pass 1 (detections->allSources)
+
+finish:
+
+    // plot positive sources
+    // psphotSourcePlots (readout, sources, recipe);
+
+    // measure aperture photometry corrections
+    if (!psphotApResid (config, view)) { // pass 1 (detections->allSources)
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // calculate source magnitudes
+    psphotMagnitudes(config, view); // pass 1 (detections->allSources)
+
+    if (!psphotEfficiency(config, view)) { // pass 1
+        psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
+        psErrorClear();
+    }
+
+    // replace failed sources?
+    // psphotReplaceUnfitSources (sources);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view); // pass 1
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (config, view); // pass 1
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, view);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutCleanup.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutCleanup.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutCleanup.c	(revision 27622)
@@ -0,0 +1,113 @@
+# include "psphotInternal.h"
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotReadoutCleanup (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // remove internal pmFPAfiles, if created
+    if (psErrorCodeLast() == PSPHOT_ERR_DATA) {
+        psErrorStackPrint(stderr, "Error in the psphot readout analysis");
+        psErrorClear();
+    }
+    if (psErrorCodeLast() != PS_ERR_NONE) {
+        return false;
+    }
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotReadoutCleanupReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on psphotReadoutCleanup for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+
+    // XXX move this to top of loop?
+    pmKapaClose ();
+
+    return true;
+}
+
+// psphotReadoutCleanup is called on exit from psphotReadout.  If the last raised error is
+// not a DATA error, then there was a serious problem.  Only in this case, or if the fail
+// on the stats measurement, do we return false
+bool psphotReadoutCleanupReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status = true;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // when psphotReadoutCleanup is called, these are not necessarily defined
+    pmPSF        *psf        = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psArray      *sources    = detections->allSources;
+    // XXX where do we free these, in here (psMetadataRemove?)
+
+    // use the psf-model to measure FWHM stats
+    if (psf) {
+        if (!psphotPSFstats (readout, psf)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure PSF shape parameters");
+            return false;
+        }
+    }
+    // otherwise, use the source moments to measure FWHM stats
+    if (!psf && sources) {
+        if (!psphotMomentsStats (readout, sources)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure Moment shape parameters");
+            return false;
+        }
+    }
+
+    // Check to see if the image quality was measured
+    // XXX not sure we want / need this test
+    if (!psf) {
+        bool mdok;                      // Status of MD lookup
+        int nIQ = psMetadataLookupS32(&mdok, recipe, "IQ_NSTAR"); // Number of stars for IQ measurement
+        if (!mdok || nIQ <= 0) {
+            psError(PSPHOT_ERR_DATA, false, "Unable to measure image quality");
+            return false;
+        }
+    }
+
+    // create an output header with stats results currently saved on readout->analysis
+    psMetadata *header = psphotDefineHeader (readout->analysis);
+
+    // write NSTARS to the image header
+    psphotSetHeaderNstars (header, sources);
+
+    // save the results of the analysis
+    // this should happen way up stream (when needed?)
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", header);
+
+    if (psf) {
+	// XXX this seems a little silly : we saved the psf on readout->analysis above, but now
+	// we are moving it to chip->analysis.
+        // save the psf for possible output.  if there was already an entry, it was loaded from external sources
+        // the new one may have been updated or modified, so replace the existing entry.  We
+        // are required to save it on the chip, but this will cause problems if we ever want to
+        // run psphot on an unmosaiced image
+        pmCell *cell = readout->parent;
+        pmChip *chip = cell->parent;
+        psMetadataAdd (chip->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_DATA_UNKNOWN | PS_META_REPLACE,  "psphot psf", psf);
+    }
+
+    if (psErrorCodeLast() != PS_ERR_NONE) {
+        psErrorStackPrint(stderr, "unexpected remaining errors");
+        abort();
+    }
+
+    psFree (header);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutFindPSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutFindPSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutFindPSF.c	(revision 27622)
@@ -0,0 +1,81 @@
+# include "psphotInternal.h"
+
+// in this psphotReadout-variant, we are only trying to determine the PSF given an existing set
+// of input source positions to use as initial PSF stars.
+bool psphotReadoutFindPSF(pmConfig *config, const pmFPAview *view, psArray *inSources) {
+
+    psTimerStart ("psphotReadout");
+
+    // set the photcode for the PSPHOT.INPUT
+    if (!psphotAddPhotcode(config, view)) {
+        psError(PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // Generate the mask and variance images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view);
+
+    // Note that in this implementation, we do NOT model the background and we do not
+    // attempt to detect the sources in the image
+
+    // include externally-supplied sources (supplied as PSPHOT.INPUT.CMF)
+    // XXX we assume a single set of input sources is supplied
+    if (!psphotDetectionsFromSources (config, view, inSources)) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Can't find PSF stars");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // construct detections->newSources and measure basic stats (moments, local sky)
+    if (!psphotSourceStats(config, view, true)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+	return false;
+    }
+
+    // peak flux is wrong : use the peak measured in the moments analysis:
+    if (!psphotRepairLoadedSources(config, view)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to repair sources");
+	return false;
+    }
+
+    // classify sources based on moments, brightness (psf is not known)
+    if (!psphotRoughClass (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough source class");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    if (!psphotImageQuality (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to measure image quality");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    if (!psphotChoosePSF(config, view)) {
+        psError(PSPHOT_ERR_PSF, false, "Failed to construct a psf model");
+        return psphotReadoutCleanup(config, view);
+    }
+
+# if 0
+    // XXX if we want to determine the aperture residual correction here, we either
+    // need to carry it out of the PSF determination analysis above, or save the model
+    // fits from that analysis, or run the linear PSF fit for all objects currently in hand
+    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
+    psphotGuessModels (config, view);
+# endif
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view); 
+
+# if 0
+    // measure aperture photometry corrections
+    if (!psphotApResid (config, view)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, view);
+    }
+# endif
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels(config, view);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, view);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutKnownSources.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutKnownSources.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutKnownSources.c	(revision 27622)
@@ -0,0 +1,74 @@
+# include "psphotInternal.h"
+
+// in this psphotReadout-variant, we are only measuring the photometry for known sources, using
+// a PSF generated for this observation from those sources
+bool psphotReadoutKnownSources(pmConfig *config, const pmFPAview *view, psArray *inSources) {
+
+    psTimerStart ("psphotReadout");
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode(config, view)) {
+        psError(PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view);
+
+    // Note that in this implementation, we do NOT model the background and we do not
+    // attempt to detect the sources in the image
+
+    // include externally-supplied sources (supplied as PSPHOT.INPUT.CMF)
+    if (!psphotDetectionsFromSources (config, view, inSources)) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Can't find PSF stars");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // construct sources and measure basic stats
+    if (!psphotSourceStats (config, view, true)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+	return false;
+    }
+
+    // peak flux is wrong : use the peak measured in the moments analysis:
+    if (!psphotRepairLoadedSources(config, view)) {
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to repair sources");
+	return false;
+    }
+
+    // classify sources based on moments, brightness (psf is not known)
+    if (!psphotRoughClass (config, view)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failed to determine rough source class");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    if (!psphotChoosePSF (config, view)) {
+        psError(PSPHOT_ERR_PSF, false, "Failed to construct a psf model");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // construct an initial model for each object
+    psphotGuessModels (config, view);
+
+    // merge the newly selected sources into the existing list
+    // NOTE: merge OLD and NEW
+    psphotMergeSources (config, view); 
+
+    // linear PSF fit to source peaks
+    psphotFitSourcesLinear (config, view, false);
+
+    // measure aperture photometry corrections
+    if (!psphotApResid (config, view)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup(config, view);
+    }
+
+    // calculate source magnitudes
+    psphotMagnitudes(config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (config, view);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, view);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutMinimal.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutMinimal.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotReadoutMinimal.c	(revision 27622)
@@ -0,0 +1,94 @@
+# include "psphotInternal.h"
+
+// this badly-named function performs photometry assuming (a) a supplied PSF, (b) background
+// subtraction, (c) linear psf-model fits only, (d) a prior analysis has supplied the moments
+// window parameters.  It is currently only being used by ppSub.
+
+// NOTE: ppSub needs to perform extended source analysis for comets and trails.
+
+bool psphotReadoutMinimal(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
+    // for psphot envisions threading within psphotReadout, not multiple threads calling
+    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
+    // jointly by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX);
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode(config, view)) {
+        psError(PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndVariance (config, view);
+
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved on readout->analysis
+    if (!psphotLoadPSF (config, view)) {
+      psError (PSPHOT_ERR_CONFIG, false, "missing psf model");
+      return psphotReadoutCleanup (config, view);
+    }
+
+    // find the detections (by peak and/or footprint) in the image. (final pass)
+    if (!psphotFindDetections(config, view, false)) {
+        psError (PSPHOT_ERR_UNKNOWN, false, "failure in peak analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // construct sources and measure basic stats (saved on detections->newSources)
+    if (!psphotSourceStats (config, view, false)) { // pass 1
+        psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate sources");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // find blended neighbors of very saturated stars
+    psphotDeblendSatstars (config, view);
+
+    // mark blended peaks PS_SOURCE_BLEND
+    if (!psphotBasicDeblend (config, view)) {
+        psLogMsg ("psphot", 3, "failed on deblend analysis");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // classify sources based on moments, brightness (use supplied psf shape parameters)
+    if (!psphotRoughClass (config, view)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, view);
+    }
+
+    // construct an initial model for each object
+    psphotGuessModels (config, view);
+
+    // merge the newly selected sources into the existing list
+    psphotMergeSources (config, view);
+
+    // linear PSF fit to source peaks
+    psphotFitSourcesLinear (config, view, false);
+
+// XXX eventually, add the extended source fits here
+# if (0)
+    // measure source size for the remaining sources
+    psphotSourceSize (config, view);
+
+    psphotExtendedSourceAnalysis (config, view);
+
+    psphotExtendedSourceFits (config, view);
+# endif
+
+    // calculate source magnitudes
+    psphotMagnitudes(config, view);
+
+    // XXX ensure this is measured if the analysis succeeds (even if quality is low)
+    if (!psphotEfficiency(config, view)) {
+        psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
+        psErrorClear();
+    }
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (config, view);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, view);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotReplaceUnfit.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotReplaceUnfit.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotReplaceUnfit.c	(revision 27622)
@@ -0,0 +1,103 @@
+# include "psphotInternal.h"
+
+// replace the flux for sources which failed
+bool psphotReplaceUnfitSources (psArray *sources, psImageMaskType maskVal) {
+
+    pmSource *source;
+
+    psTimerStart ("psphot.replace");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (source->mode & PM_SOURCE_MODE_FAIL) goto replace;
+      continue;
+
+    replace:
+        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    psLogMsg ("psphot.replace", 3, "replace unfitted models: %f sec (%ld objects)\n", psTimerMark ("psphot.replace"), sources->n);
+    return true;
+}
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotReplaceAllSources (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotReplaceAllSourcesReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to replace all sources for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotReplaceAllSourcesReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status;
+    pmSource *source;
+
+    psTimerStart ("psphot.replace");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    psAssert (maskVal, "missing mask value?");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) continue;
+
+      pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    psLogMsg ("psphot.replace", PS_LOG_INFO, "replaced models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.replace"));
+    return true;
+}
+
+bool psphotRemoveAllSources (const psArray *sources, const psMetadata *recipe) {
+
+    bool status;
+    pmSource *source;
+
+    psTimerStart ("psphot.replace");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) continue;
+
+      pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    psLogMsg ("psphot.replace", PS_LOG_INFO, "replaced models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot.replace"));
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotRoughClass.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotRoughClass.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotRoughClass.c	(revision 27622)
@@ -0,0 +1,180 @@
+# include "psphotInternal.h"
+
+# define CHECK_STATUS(S,MSG) {                                          \
+        if (!status) {                                                  \
+            psError(PSPHOT_ERR_CONFIG, false, "missing PSF Clump entry: %s\n", MSG); \
+            return false;                                               \
+        } }
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotRoughClass (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotRoughClassReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on rough classification for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotRoughClassReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe) {
+
+    bool status;
+
+    psTimerStart ("psphot.rough");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // if we have a PSF, use the existing PSF clump region below
+    bool havePSF = false;
+    if (psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF")) {
+	havePSF = true;
+    }
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->newSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping rough classification");
+	return true;
+    }
+
+    // we make this measurement on a NxM grid of regions across the readout
+    int NX  = psMetadataLookupS32 (&status, recipe, "PSF_CLUMP_NX");  CHECK_STATUS (status, "PSF_CLUMP_NX");
+    int NY  = psMetadataLookupS32 (&status, recipe, "PSF_CLUMP_NY");  CHECK_STATUS (status, "PSF_CLUMP_NY");
+    int dX  = readout->image->numCols / NX;
+    int dY  = readout->image->numRows / NY;
+
+    int nRegion = 0;
+    for (int ix = 0; ix < NX; ix ++) {
+        for (int iy = 0; iy < NY; iy ++) {
+
+            psRegion *region = psRegionAlloc (ix*dX, (ix + 1)*dX, iy*dY, (iy + 1)*dY);
+            if (!psphotRoughClassRegion (nRegion, region, sources, readout->analysis, recipe, havePSF)) {
+                psLogMsg ("psphot", 4, "Failed to determine rough classification for region %f,%f - %f,%f\n",
+                         region->x0, region->y0, region->x1, region->y1);
+                psFree (region);
+                continue;
+            }
+            psFree (region);
+            nRegion ++;
+        }
+    }
+    psMetadataAddS32 (readout->analysis, PS_LIST_TAIL, "PSF.CLUMP.NREGIONS",  PS_META_REPLACE, "psf clump regions", nRegion);
+
+    // optional printout of source moments only
+    psphotDumpMoments (recipe, sources);
+
+    psLogMsg ("psphot.roughclass", PS_LOG_INFO, "rough classification: %f sec\n", psTimerMark ("psphot.rough"));
+
+    psphotVisualPlotMoments (recipe, readout->analysis, sources);
+    psphotVisualShowRoughClass (sources);
+    // XXX better visualization: psphotVisualShowFlags (sources);
+
+    return true;
+}
+
+bool psphotRoughClassRegion (int nRegion, psRegion *region, psArray *sources, psMetadata *analysis, psMetadata *recipe, const bool havePSF) {
+
+    bool status;
+    char regionName[64];
+    pmPSFClump psfClump;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskSat = psMetadataLookupImageMask(&status, recipe, "MASK.SAT"); // Mask value for bad pixels
+    assert (maskSat);
+
+    // the regions are saved on the readout->analysis metadata folder (passed to us as analysis)
+    snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", nRegion);
+    psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+    if (!regionMD) {
+        // allocate the region metadata folder and add this region to it.
+        regionMD = psMetadataAlloc();
+        psMetadataAddMetadata (analysis, PS_LIST_TAIL, regionName, PS_META_REPLACE, "psf clump region", regionMD);
+        psFree (regionMD);
+    }
+    psMetadataAddPtr (regionMD, PS_LIST_TAIL, "REGION", PS_DATA_REGION | PS_META_REPLACE, "psf clump region", region);
+
+    if (!havePSF) {
+        // determine the PSF parameters from the source moment values
+        // XXX why not save the psfClump as a PTR?
+
+	float PSF_SN_LIM = psMetadataLookupF32(&status, recipe, "PSF_SN_LIM"); psAssert (status, "missing PSF_SN_LIM");
+	float MOMENTS_AR_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_AR_MAX"); psAssert (status, "missing MOMENTS_AR_MAX");
+
+	float PSF_CLUMP_GRID_SCALE = psMetadataLookupF32(&status, analysis, "PSF_CLUMP_GRID_SCALE");
+	if (!status) {
+	    PSF_CLUMP_GRID_SCALE = psMetadataLookupF32(&status, recipe, "PSF_CLUMP_GRID_SCALE");
+	    psAssert (status, "missing PSF_CLUMP_GRID_SCALE");
+	}
+	float MOMENTS_SX_MAX = psMetadataLookupF32(&status, analysis, "MOMENTS_SX_MAX");
+	if (!status) {
+	    MOMENTS_SX_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_SX_MAX");
+	    psAssert (status, "missing MOMENTS_SX_MAX");
+	}
+	float MOMENTS_SY_MAX = psMetadataLookupF32(&status, analysis, "MOMENTS_SY_MAX");
+	if (!status) {
+	    MOMENTS_SY_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_SY_MAX");
+	    psAssert (status, "missing MOMENTS_SY_MAX");
+	}
+
+        psfClump = pmSourcePSFClump (NULL, region, sources, PSF_SN_LIM, PSF_CLUMP_GRID_SCALE, MOMENTS_SX_MAX, MOMENTS_SY_MAX, MOMENTS_AR_MAX);
+
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
+    } else {
+        // pull FWHM_X,Y from the recipe, use to define psfClump.X,Y
+        psfClump.X  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");
+        if (!status) {
+            psLogMsg ("psphot", 4, "No PSF clump defined for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
+            return false;
+        }
+        psfClump.Y  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");   psAssert (status, "missing PSF.CLUMP.Y");
+        psfClump.dX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");  psAssert (status, "missing PSF.CLUMP.DX");
+        psfClump.dY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");  psAssert (status, "missing PSF.CLUMP.DY");
+    }
+
+    if (psfClump.X < 0) {
+        psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourcePSFClump");
+        return false;
+    }
+    if (!psfClump.X || !psfClump.Y || isnan(psfClump.X) || isnan(psfClump.Y)) {
+        psLogMsg ("psphot", 4, "Failed to find a valid PSF clump for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
+        return false;
+    }
+    psLogMsg ("psphot", 3, "psf clump  X,  Y: %f, %f\n", psfClump.X, psfClump.Y);
+    psLogMsg ("psphot", 3, "psf clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
+
+    // get basic parameters, or set defaults
+    float PSF_SN_LIM = psMetadataLookupF32 (&status, recipe, "PSF_SN_LIM"); psAssert (status, "missing PSF_SN_LIM");
+    float PSF_CLUMP_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_CLUMP_NSIGMA"); psAssert (status, "missing PSF_CLUMP_NSIGMA");
+
+    // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
+    if (!pmSourceRoughClass (region, sources, PSF_SN_LIM, PSF_CLUMP_NSIGMA, psfClump, maskSat)) {
+        psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourceRoughClass");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSavePSFStars.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSavePSFStars.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSavePSFStars.c	(revision 27622)
@@ -0,0 +1,148 @@
+# include "psphotInternal.h"
+
+bool psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    // bool status = false;
+    psTimerStart ("psphot");
+
+    // if this file is defined, create the necessary output data
+    pmFPAfile *file = psMetadataLookupPtr(&status, config->files, "PSPHOT.PSF.STARS");
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 21;
+    int DY = 21;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;				// starting corner of next box
+    int dY = 0;				// height of row so far
+    int NX = 20*DX;			// full width of output image
+    int NY = 0;				// total height of output image
+
+    // first, examine the PSF and SAT stars:
+    // - determine bounding boxes for summary image
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+	bool keep = false;
+        keep |= (source->mode & PM_SOURCE_MODE_PSFSTAR);
+        keep |= (source->mode & PM_SOURCE_MODE_SATSTAR);
+	if (!keep) continue;
+
+	// how does this subimage get placed into the output image?
+	// DX = source->pixels->numCols
+	// DY = source->pixels->numRows
+
+	if (dX + DX > NX) {
+	    // too wide for the rest of this row
+	    if (dX == 0) {
+		// alone on this row
+		NY += DY;
+		dX = 0;
+		dY = 0;
+	    } else {
+		// start the next row
+		NY += dY;
+		dX = DX;
+		dY = DY;
+	    }
+	} else {
+	    // extend this row
+	    dX += DX;
+	    dY = PS_MAX (dY, DY);
+	}
+    }
+
+    // allocate output image
+    psImage *outpos = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImage *outsub = psImageAlloc (NX, NY, PS_TYPE_F32);
+
+    int Xo = 0;				// starting corner of next box
+    int Yo = 0;				// starting corner of next box
+    dY = 0;				// height of row so far
+
+    int nPSF = 0;
+    int nSAT = 0;
+    int kapa = 0;			// file descriptor for plotting routine
+
+    // first, examine the PSF and SAT stars:
+    // - generate radial plots (PS plots)
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+	bool keep = false;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    nPSF ++;
+	    keep = true;
+	}
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    nSAT ++;
+	    keep = true;
+	}	    
+	if (!keep) continue;
+
+	// how does this subimage get placed into the output image?
+	// DX = source->pixels->numCols
+	// DY = source->pixels->numRows
+
+	if (Xo + DX > NX) {
+	    // too wide for the rest of this row
+	    if (Xo == 0) {
+		// place source alone on this row
+		psphotAddWithTest (source, true); // replace source if subtracted
+		psphotRadialPlot (&kapa, "radial.plots.ps", source);
+		psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+		psphotSubWithTest (source, false); // remove source (force)
+		psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+
+		psphotSetState (source, false); // replace source (has been subtracted)
+		Yo += DY;
+		Xo = 0;
+		dY = 0;
+	    } else {
+		// start the next row
+		Yo += dY;
+		Xo = 0;
+		psphotAddWithTest (source, true); // replace source if subtracted
+		psphotRadialPlot (&kapa, "radial.plots.ps", source);
+		psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+		psphotSubWithTest (source, false); // remove source (force)
+		psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+		psphotSetState (source, false); // replace source (has been subtracted)
+
+		Xo = DX;
+		dY = DY;
+	    }
+	} else {
+	    // extend this row
+	    psphotAddWithTest (source, true); // replace source if subtracted
+	    psphotRadialPlot (&kapa, "radial.plots.ps", source);
+	    psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+	    psphotSubWithTest (source, false); // remove source (force)
+	    psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+	    psphotSetState (source, false); // replace source (has been subtracted)
+
+	    Xo += DX;
+	    dY = PS_MAX (dY, DY);
+	}
+    }
+
+    psphotSaveImage (NULL, outpos, "outpos.fits");
+    psphotSaveImage (NULL, outsub, "outsub.fits");
+    psLogMsg ("psphot", PS_LOG_INFO, "plotted %d sources (%d psf, %d sat): %f sec\n", nPSF + nSAT, nPSF, nSAT, psTimerMark ("psphot"));
+
+    psFree (outpos);
+    psFree (outsub);
+    return (sources);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSetMaskBits.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSetMaskBits.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSetMaskBits.c	(revision 27622)
@@ -0,0 +1,38 @@
+# include "psphotInternal.h"
+
+// XXX Should it be an error for any of these to not exist?
+
+// This function is called by the stand-alone psphot program to set the mask values in the
+// config file.  It sets the named mask values MASK.PSPHOT and MARK.PSPHOT in the PSPHOT
+// recipe.  Functions or programs which call psphotReadout as a library function must set these
+// named mask values in the PSPHOT recipe on their own.
+
+bool psphotSetMaskBits (pmConfig *config) {
+
+    psImageMaskType maskValue;
+    psImageMaskType markValue;
+
+    if (!pmConfigMaskSetBits (&maskValue, &markValue, config)) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the mask bit values");
+	return false;
+    }
+
+    bool status = psphotSetMaskRecipe (config, maskValue, markValue);
+    return status;
+}
+
+bool psphotSetMaskRecipe (pmConfig *config, psImageMaskType maskValue, psImageMaskType markValue) {
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set maskValue and markValue in the psphot recipe
+    psMetadataAddImageMask (recipe, PS_LIST_TAIL, "MARK.PSPHOT", PS_META_REPLACE, "user-defined mask", markValue);
+    psMetadataAddImageMask (recipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", maskValue);
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSetThreads.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSetThreads.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSetThreads.c	(revision 27622)
@@ -0,0 +1,38 @@
+# include "psphot.h"
+
+bool psphotSetThreads () {
+
+    psThreadTask *task = NULL;
+
+    task = psThreadTaskAlloc("PSPHOT_GUESS_MODEL", 5);
+    task->function = &psphotGuessModel_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_MAGNITUDES", 9);
+    task->function = &psphotMagnitudes_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_PSF_WEIGHTS", 2);
+    task->function = &psphotPSFWeights_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_APRESID_MAGS", 7);
+    task->function = &psphotApResidMags_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_SOURCE_STATS", 10);
+    task->function = &psphotSourceStats_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    task = psThreadTaskAlloc("PSPHOT_BLEND_FIT", 9);
+    task->function = &psphotBlendFit_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSignificanceImage.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSignificanceImage.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSignificanceImage.c	(revision 27622)
@@ -0,0 +1,137 @@
+# include "psphotInternal.h"
+
+// In this function, we smooth the image and variance, then generate the significance image :
+// (S/N)^2.  If FWMH_X,Y have been recorded, use them, otherwise use PEAKS_SMOOTH_SIGMA for the
+// smoothing kernel.
+psImage *psphotSignificanceImage (pmReadout *readout, psMetadata *recipe, const int pass, psImageMaskType maskVal) {
+
+    float SIGMA_SMTH, NSIGMA_SMTH;
+    bool status = false;
+    bool guess = false;
+
+    // smooth the image and variance map
+    psTimerStart ("psphot.smooth");
+    bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading in psImageConvolve
+
+    // XXX we can a) choose fft to convolve if needed and b) multithread fftw
+
+    float minGauss = psMetadataLookupF32(NULL, recipe, "PEAKS_MIN_GAUSS"); // Minimum valid fraction of kernel
+    if (!isfinite(minGauss)) {
+        psWarning("PEAKS_MIN_GAUSS is not set in recipe; using default value");
+        minGauss = 0.5;
+    }
+
+    // if we have already determined the PSF model, then we have a better idea how to smooth this image
+    bool statusMajor, statusMinor;
+    float fwhmMajor = psMetadataLookupF32(&statusMajor, readout->analysis, "FWHM_MAJ");
+    float fwhmMinor = psMetadataLookupF32(&statusMinor, readout->analysis, "FWHM_MIN");
+    if (statusMajor && statusMinor) {
+        // if we know the FHWM, use that to set the smoothing kernel (XXX allow an optional override?)
+        if (!isfinite(fwhmMajor) || !isfinite(fwhmMinor) || fwhmMajor == 0.0 || fwhmMinor == 0.0) {
+            psWarning("fwhmMajor (%f) or fwhmMinor (%f) is bad!", fwhmMajor, fwhmMinor);
+        }
+        SIGMA_SMTH  = 0.5*(fwhmMajor + fwhmMinor) / (2.0*sqrt(2.0*log(2.0)));
+        NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+        guess = false;
+    } else {
+        // if we do not know the FWHM, use the guess smoothing kernel supplied.
+        // it is a configuration error if these are not supplied
+        SIGMA_SMTH  = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_SIGMA");
+        PS_ASSERT (status, NULL);
+        NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+        PS_ASSERT (status, NULL);
+        guess = true;
+    }
+    // record the actual smoothing sigma
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SIGMA_SMOOTH", PS_META_REPLACE, "Smoothing sigma for detections", SIGMA_SMTH);
+
+    // smooth the image, applying the mask as we go
+    psImage *smooth_im = psImageCopy(NULL, readout->image, PS_TYPE_F32);
+    psImageSmoothMask_Threaded(smooth_im, smooth_im, readout->mask, maskVal, SIGMA_SMTH, NSIGMA_SMTH, minGauss);
+    psLogMsg("psphot", PS_LOG_MINUTIA, "smooth image: %f sec\n", psTimerMark("psphot.smooth"));
+
+    // Smooth the variance, applying the mask as we go.  The variance is smoothed by the PSF^2,
+    // renomalized to maintain the input level of the variance.  We achieve this by smoothing
+    // with a Gaussian with sigma = SIGMA_SMTH/sqrt(2) with unity normalization.  Note that
+    // this process yields a smoothed image with correlated errors.  The pixel-to-pixel
+    // variations in smooth_im will be decreased by a factor of 4*pi*SIGMA_SMTH^2, but for
+    // measurements based on apertures comparable to or larger than the smoothing kernel, the
+    // effective per-pixel variance is maintained.
+    psImage *smooth_wt = psImageCopy(NULL, readout->variance, PS_TYPE_F32);
+    psImageSmoothMask_Threaded(smooth_wt, smooth_wt, readout->mask, maskVal, SIGMA_SMTH * M_SQRT1_2, NSIGMA_SMTH, minGauss);
+    psLogMsg("psphot", PS_LOG_MINUTIA, "smooth variance: %f sec\n", psTimerMark("psphot.smooth"));
+
+    psImage *mask = readout->mask;
+
+    // optionally save example images under trace
+    // XXX change these to recipe value checks
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[64];
+        sprintf (name, "imsmooth.v%d.fits", pass);
+        psphotSaveImage(NULL, smooth_im, name);
+        sprintf (name, "wtsmooth.v%d.fits", pass);
+        psphotSaveImage(NULL, smooth_wt, name);
+    }
+
+    // Calculate correction factor for the covariance produced by the (potentially multiple) smoothing
+    psKernel *kernel = psImageSmoothKernel(SIGMA_SMTH, NSIGMA_SMTH); // Kernel used for smoothing
+    float factor = 1.0 / psImageCovarianceCalculateFactor(kernel, readout->covariance);
+    psFree(kernel);
+
+    // record the effective area and significance scaling factor
+    float effArea = 8.0 * M_PI * PS_SQR(SIGMA_SMTH);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "EFFECTIVE_AREA", PS_META_REPLACE, "Effective Area", effArea);
+    psMetadataAddF32(readout->analysis, PS_LIST_TAIL, "SIGNIFICANCE_SCALE_FACTOR", PS_META_REPLACE, "Signicance scale factor", factor);
+
+    // XXX multithread this if needed
+    for (int j = 0; j < smooth_im->numRows; j++) {
+        for (int i = 0; i < smooth_im->numCols; i++) {
+            float value = smooth_im->data.F32[j][i];
+            if (value < 0 || smooth_wt->data.F32[j][i] <= 0 || (mask->data.PS_TYPE_IMAGE_MASK_DATA[j][i] & maskVal)) {
+                smooth_im->data.F32[j][i] = 0.0;
+            } else {
+                smooth_im->data.F32[j][i] = factor * PS_SQR(value) / smooth_wt->data.F32[j][i];
+            }
+        }
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "built smoothed signficance image: %f sec\n", psTimerMark("psphot.smooth"));
+
+    // optionally save example images under trace
+    // XXX change these to recipe value checks
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[64];
+        sprintf (name, "snsmooth.v%d.fits", pass);
+        psphotSaveImage (NULL, smooth_im, name);
+    }
+
+    psFree(smooth_wt);
+
+    psImageConvolveSetThreads(oldThreads);
+
+    return smooth_im;
+}
+
+# if (0)
+{
+    // threadingdemo
+    // smooth the image, applying the mask as we go
+    psImage *smooth_im = psImageCopy(NULL, readout->image, PS_TYPE_F32);
+    psImageSmoothMask(smooth_im, smooth_im, readout->mask, maskVal, SIGMA_SMTH, NSIGMA_SMTH, minGauss);
+
+    psLogMsg("psphot", PS_LOG_INFO, "smooth image: %f sec\n", psTimerMark("psphot.smooth"));
+
+    psImageConvolveSetThreads(true);
+
+    psTimerStart ("psphot.smooth");
+
+    // XXX a quick test of the threaded version of the function:
+    psImage *smooth_test = psImageCopy(NULL, readout->image, PS_TYPE_F32);
+    psImageSmoothMask_Threaded(smooth_test, smooth_test, readout->mask, maskVal, SIGMA_SMTH, NSIGMA_SMTH, minGauss);
+
+    psLogMsg("psphot", PS_LOG_INFO, "smooth_threaded image: %f sec\n", psTimerMark("psphot.smooth"));
+
+    psphotSaveImage (NULL, smooth_im, "smooth_im.fits");
+    psphotSaveImage (NULL, smooth_test, "smooth_test.fits");
+    exit (0);
+}
+# endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSkyReplace.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSkyReplace.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSkyReplace.c	(revision 27622)
@@ -0,0 +1,53 @@
+# include "psphotInternal.h"
+
+bool psphotSkyReplace (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSkyReplaceReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to replace sky for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// XXX make this an option?
+// in order to  successfully replace the sky, we must define a corresponding file...
+bool psphotSkyReplaceReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index) {
+
+    psTimerStart ("psphot.skyreplace");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    // select background pixels, from output background file, or create
+    pmReadout *background = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKGND");
+    if (background == NULL) psAbort("background not defined");
+
+    // select the corresponding images
+    psF32 **image = readout->image->data.F32;
+    psImageMaskType  **mask  = readout->mask->data.PS_TYPE_IMAGE_MASK_DATA;
+    psF32 **back  = background->image->data.F32;
+
+    // replace the background model
+    for (int j = 0; j < readout->image->numRows; j++) {
+        for (int i = 0; i < readout->image->numCols; i++) {
+            if (!mask[j][i]) {
+                image[j][i] += back[j][i];
+            }
+        }
+    }
+    psLogMsg ("psphot.sky", PS_LOG_DETAIL, "replace background flux : %f sec\n", psTimerMark ("psphot.skyreplace"));
+    return true;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSortBySN.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSortBySN.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSortBySN.c	(revision 27622)
@@ -0,0 +1,1 @@
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceFits.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceFits.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceFits.c	(revision 27622)
@@ -0,0 +1,453 @@
+# include "psphotInternal.h"
+
+// given a source with an existing modelPSF, attempt a full PSF fit, subtract if successful
+// XXX this function does not call pmSourceFitModelInit : fix this?
+
+static int NfitPSF = 0;
+static int NfitBlend = 0;
+static int NfitDBL = 0;
+static int NfitEXT = 0;
+
+bool psphotFitInit (int nThreads) {
+    psTimerStart ("psphot.fits");
+    pmSourceFitSetInit (nThreads);
+    return true;
+}
+
+bool psphotFitSummary () {
+
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "fitted %5d psf, %5d blend, %5d ext, %5d dbl : %6.2f sec\n",
+             NfitPSF, NfitBlend, NfitEXT, NfitDBL, psTimerMark ("psphot.fits"));
+    return true;
+}
+
+bool psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    float x, y, dR;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // if this source is not a possible blend, just fit as PSF
+    if ((source->blends == NULL) || (source->mode & PM_SOURCE_MODE_SATSTAR)) {
+        bool status = psphotFitPSF (readout, source, psf, maskVal, markVal);
+        return status;
+    }
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+
+    if (isnan(PSF->params->data.F32[PM_PAR_I0])) psAbort("nan in blend fit primary");
+
+    x = PSF->params->data.F32[PM_PAR_XPOS];
+    y = PSF->params->data.F32[PM_PAR_YPOS];
+
+    psArray *modelSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (modelSet, 16, PSF);
+
+    psArray *sourceSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (sourceSet, 16, source);
+
+    psTrace ("psphot", 4, "fitting blended source at %f %f : %f\n", source->peak->xf, source->peak->yf, source->peak->flux);
+
+    // we need to include all blends in the fit (unless primary is saturated?)
+    dR = 0;
+    for (int i = 0; i < source->blends->n; i++) {
+        pmSource *blend = source->blends->data[i];
+
+        // find the blend which is furthest from source
+        dR = PS_MAX (dR, hypot (blend->peak->xf - x, blend->peak->yf - y));
+
+        // create the model and guess parameters for this blend
+        pmModel *model = pmModelAlloc (PSF->type);
+        for (int j = 0; j < model->params->n; j++) {
+            model->params->data.F32[j] = PSF->params->data.F32[j];
+            model->dparams->data.F32[j] = PSF->dparams->data.F32[j];
+        }
+
+        // XXX assume local sky is 0.0?
+        model->params->data.F32[PM_PAR_I0] = blend->peak->flux;
+        model->params->data.F32[PM_PAR_XPOS] = blend->peak->xf;
+        model->params->data.F32[PM_PAR_YPOS] = blend->peak->yf;
+
+        // these should never be invalid values
+        // XXX drop these tests eventually
+        if (isnan(model->params->data.F32[PM_PAR_I0]))   psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_XPOS])) psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_YPOS])) psAbort("nan in blend fit");
+
+        // add this blend to the list
+        psArrayAdd (modelSet, 16, model);
+        psArrayAdd (sourceSet, 16, blend);
+
+	psTrace ("psphot", 5, "adding source at %f %f : %f\n", blend->peak->xf, blend->peak->yf, blend->peak->flux);
+
+        // free to avoid double counting model
+        psFree (model);
+    }
+
+    // extend source radius as needed
+    psphotCheckRadiusPSFBlend (readout, source, PSF, markVal, dR);
+
+    // fit PSF model
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF, maskVal);
+
+    // clear the circular mask
+    psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 
+
+    if (!isfinite(PSF->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
+
+    // correct model chisq for flux trend
+    double chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // evaluate the blend objects, subtract if good, free otherwise
+    for (int i = 1; i < modelSet->n; i++) {
+        pmSource *blend = sourceSet->data[i];
+        pmModel *model  = modelSet->data[i];
+
+	if (!isfinite(model->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
+
+        // correct model chisq for flux trend
+        chiTrend = psPolynomial1DEval (psf->ChiTrend, model->params->data.F32[PM_PAR_I0]);
+        model->chisqNorm = model->chisq / chiTrend;
+
+        // if this one failed, skip it
+        if (!psphotEvalPSF (blend, model)) {
+            psTrace ("psphot", 4, "failed on blend %d of %ld\n", i, modelSet->n);
+            continue;
+        }
+
+        // otherwise, supply the resulting model to the corresponding blend
+        psFree(blend->modelPSF);
+        blend->modelPSF = psMemIncrRefCounter (model);
+        psTrace ("psphot", 5, "fitted blend as PSF\n");
+
+        // build cached model and subtract
+        pmSourceCacheModel (blend, maskVal);
+        pmSourceSub (blend, PM_MODEL_OP_FULL, maskVal);
+        blend->mode |=  PM_SOURCE_MODE_BLEND_FIT;
+    }
+    NfitBlend += modelSet->n;
+
+    // evaluate the primary object
+    if (!psphotEvalPSF (source, PSF)) {
+        psTrace ("psphot", 4, "failed on blend 0 of %ld\n", modelSet->n);
+        psFree (PSF);
+        psFree (modelSet);
+        psFree (sourceSet);
+        return false;
+    }
+    psFree (modelSet);
+    psFree (sourceSet);
+
+    // save the new, successful model
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    psTrace ("psphot", 5, "fitted primary as PSF\n");
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    source->mode |=  PM_SOURCE_MODE_BLEND_FIT;
+    return true;
+}
+
+bool psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    double chiTrend;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    NfitPSF ++;
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in psf fit");
+
+    // extend source radius as needed
+    psphotCheckRadiusPSF (readout, source, PSF, markVal);
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitModel (source, PSF, PM_SOURCE_FIT_PSF, maskVal);
+
+    if (!isfinite(PSF->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
+
+    // clear the circular mask
+    psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 
+
+    // correct model chisq for flux trend
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // does the PSF model succeed?
+    if (!psphotEvalPSF (source, PSF)) {
+        psFree (PSF);
+        return false;
+    }
+
+    // free old model, save new model
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    psTrace ("psphot", 5, "fitted as PSF\n");
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    return true;
+}
+
+static pmModelType modelTypeEXT;
+
+bool psphotInitLimitsEXT (psMetadata *recipe) {
+
+    bool status;
+
+    // extended source model descriptions
+    char *modelNameEXT = psMetadataLookupStr (&status, recipe, "EXT_MODEL");
+    modelTypeEXT = pmModelClassGetType (modelNameEXT);
+    psphotInitRadiusEXT (recipe, modelTypeEXT);
+
+    return true;
+}
+
+bool psphotFitBlob (pmReadout *readout, pmSource *source, psArray *newSources, pmPSF *psf, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    bool okEXT, okDBL;
+    float chiEXT, chiDBL;
+    double chiTrend;
+    pmModel *ONE = NULL;
+    pmSource *tmpSrc = NULL;
+    pmModel *EXT = NULL;
+    psArray *DBL = NULL;
+
+    // skip the source if we don't think it is extended
+    if (source->type == PM_SOURCE_TYPE_UNKNOWN) return false;
+    if (source->type == PM_SOURCE_TYPE_DEFECT) return false;
+    if (source->type == PM_SOURCE_TYPE_SATURATED) return false;
+
+    // set the radius based on the footprint (also sets the mask pixels)
+    float radius = psphotSetRadiusEXT (readout, source, markVal);
+
+    // XXX note that this changes the source moments that are published...
+    // recalculate the source moments using the larger extended-source moments radius
+    // at this stage, skip Gaussian windowing, and do not clip pixels by S/N
+    // this uses the footprint to judge both radius and aperture?
+    if (!pmSourceMoments (source, radius, 0.0, 0.0, maskVal)) return false;
+
+    psTrace ("psphot", 5, "trying blob...\n");
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // this temporary source is used as a place-holder by the psphotEval functions below
+    tmpSrc = pmSourceAlloc ();
+
+    // XXX need to handle failures better here
+    EXT = psphotFitEXT (readout, source, modelTypeEXT, maskVal, markVal);
+    if (!EXT) goto escape;
+    if (!isfinite(EXT->params->data.F32[PM_PAR_I0])) goto escape;
+
+    okEXT = psphotEvalEXT (tmpSrc, EXT);
+    chiEXT = EXT ? EXT->chisq / EXT->nDOF : NAN;
+
+    // DBL will always be defined, but DBL->data[n] might not
+    DBL = psphotFitDBL (readout, source, maskVal, markVal);
+    if (!DBL) goto escape;
+    if (!DBL->n) goto escape;
+
+    okDBL  = psphotEvalDBL (tmpSrc, DBL->data[0]);
+    okDBL &= psphotEvalDBL (tmpSrc, DBL->data[1]);
+    // XXX should I keep / save the flags set in the eval functions?
+
+    // clear the circular mask
+    psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 
+
+    // correct first model chisqs for flux trend
+    chiDBL = NAN;
+    ONE = DBL->data[0];
+    if (ONE) {
+	if (!isfinite(ONE->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
+      chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+      ONE->chisqNorm = ONE->chisq / chiTrend;
+      chiDBL = ONE->chisq / ONE->nDOF; // save chisq for double-star/galaxy comparison
+    }
+
+    // correct second model chisqs for flux trend
+    ONE = DBL->data[1];
+    if (ONE) {
+	if (!isfinite(ONE->params->data.F32[PM_PAR_I0])) psAbort("nan in fit");
+      chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+      ONE->chisqNorm = ONE->chisq / chiTrend;
+    }
+
+    psFree (tmpSrc);
+
+    // (void) psTraceSetLevel("psModules.objects.pmSourceFitSet", 0);
+
+    if (okEXT && okDBL) {
+        psTrace ("psphot", 5, "blob chisq: %f vs %f\n", chiEXT, chiDBL);
+        // XXX EAM : a bogus bias: need to examine this better
+        if (3*chiEXT > chiDBL) goto keepDBL;
+        goto keepEXT;
+    }
+
+    if (okEXT && !okDBL) goto keepEXT;
+    if (!okEXT && okDBL) goto keepDBL;
+
+    // both models failed; reject them both
+    // XXX -- change type flags to psf in this case and keep original moments?
+    psFree (EXT);
+    psFree (DBL);
+    return false;
+
+keepEXT:
+    // sub EXT
+    psFree (DBL);
+
+    // save new model
+    source->modelEXT = EXT;
+    source->modelEXT->fitRadius = radius;
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+    source->mode |= PM_SOURCE_MODE_EXTMODEL;
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+# if (PS_TRACE_ON)   
+    psTrace ("psphot", 5, "blob as EXT: %f %f\n", EXT->params->data.F32[PM_PAR_XPOS], EXT->params->data.F32[PM_PAR_YPOS]);
+    if (psTraceGetLevel("psphot") >= 6) {
+	psLogMsg ("psphot", 1, "source 2:\n");
+	for (int i = 0; i < source->modelEXT->params->n; i++) {
+	    psLogMsg ("psphot", 1, "PAR %d : %f +/- %f\n", i, source->modelEXT->params->data.F32[i], source->modelEXT->dparams->data.F32[i]);
+	}
+    }
+# endif
+
+    return true;
+
+keepDBL:
+    // sub DLB
+    psFree (EXT);
+
+    // drop old model, save new second model...
+    psFree (source->modelPSF);
+    source->modelPSF = psMemIncrRefCounter (DBL->data[0]);
+    source->mode     |= PM_SOURCE_MODE_PAIR;
+    source->modelPSF->fitRadius = radius;
+
+    // copy most data from the primary source (modelEXT, blends stay NULL)
+    pmSource *newSrc = pmSourceCopy (source);
+    newSrc->modelPSF = psMemIncrRefCounter (DBL->data[1]);
+    newSrc->modelPSF->fitRadius = radius;
+
+    // build cached models and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    pmSourceCacheModel (newSrc, maskVal);
+    pmSourceSub (newSrc, PM_MODEL_OP_FULL, maskVal);
+
+# if (PS_TRACE_ON)   
+    psTrace ("psphot", 5, "blob as DBL: %f %f\n", ONE->params->data.F32[PM_PAR_XPOS], ONE->params->data.F32[PM_PAR_YPOS]);
+    if (psTraceGetLevel("psphot") >= 6) {
+	psLogMsg ("psphot", 1, "source 1:\n");
+	for (int i = 0; i < newSrc->modelPSF->params->n; i++) {
+	    psLogMsg ("psphot", 1, "PAR %d : %f +/- %f\n", i, newSrc->modelPSF->params->data.F32[i], newSrc->modelPSF->dparams->data.F32[i]);
+	}
+	psLogMsg ("psphot", 1, "source 2:\n");
+	for (int i = 0; i < source->modelPSF->params->n; i++) {
+	    psLogMsg ("psphot", 1, "PAR %d : %f +/- %f\n", i, source->modelPSF->params->data.F32[i], source->modelPSF->dparams->data.F32[i]);
+	}
+	psphotVisualShowResidualImage (readout);
+    }
+# endif
+
+    psArrayAdd (newSources, 100, newSrc);
+    psFree (newSrc);
+    psFree (DBL);
+    return true;
+
+escape:
+    psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 
+    psFree (tmpSrc);
+    psFree (EXT);
+    psFree (DBL);
+    return false;
+}
+
+// fit a double PSF source to an extended blob
+psArray *psphotFitDBL (pmReadout *readout, pmSource *source, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    float dx, dy;
+    pmModel *DBL;
+    pmModel *PSF;
+    psEllipseAxes axes;
+    psEllipseMoments moments;
+    psArray *modelSet;
+
+    NfitDBL ++;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // XXX this is really poor: if we don't have moments for the source, we have no guess.
+    // force the measurement?
+    psAssert (source->moments, "moments are re-calculated for any extended source");
+	
+    // make a guess at the position of the two sources
+    moments.x2 = source->moments->Mxx;
+    moments.xy = source->moments->Mxy;
+    moments.y2 = source->moments->Myy;
+    axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    // XXX this is really arbitrary: 4 pixel separation?
+    dx = 2 * cos (axes.theta);
+    dy = 2 * sin (axes.theta);
+
+    // save the PSF model from the Ensemble fit
+    PSF = source->modelPSF;
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in dbl fit");
+
+    modelSet = psArrayAlloc (2);
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf + dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf + dy;
+    modelSet->data[0] = DBL;
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf - dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf - dy;
+    modelSet->data[1] = DBL;
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF, maskVal);
+    return (modelSet);
+}
+
+pmModel *psphotFitEXT (pmReadout *readout, pmSource *source, pmModelType modelType, psImageMaskType maskVal, psImageMaskType markVal) {
+
+    NfitEXT ++;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *EXT = pmSourceModelGuess (source, modelType);
+    if (!EXT) {
+	psTrace ("psphot", 5, "failed to generate a model for source: moments: %f %f\n", source->moments->Mxx, source->moments->Myy);
+	return NULL;
+    }
+
+    if ((source->moments->Mxx < 1e-3) || (source->moments->Myy < 1e-3)) {
+        psTrace ("psphot", 5, "problem source: moments: %f %f\n", source->moments->Mxx, source->moments->Myy);
+    }
+
+    // fit EXT (not PSF) model (set/unset the pixel mask)
+    pmSourceFitModel (source, EXT, PM_SOURCE_FIT_EXT, maskVal);
+    return (EXT);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceFreePixels.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceFreePixels.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceFreePixels.c	(revision 27622)
@@ -0,0 +1,42 @@
+# include "psphotInternal.h"
+
+bool psphotSourceFreePixels (pmConfig *config, const pmFPAview *view)
+{
+    bool status = true;
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSourceFreePixelsReadout (config, view, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to free source pixels for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+bool psphotSourceFreePixelsReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index) {
+
+    bool status;
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	pmSourceFreePixels (source);
+    }
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceMatch.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceMatch.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceMatch.c	(revision 27622)
@@ -0,0 +1,50 @@
+# include "psphotInternal.h"
+                                                                                                                                                                               
+// we need a couple of functions to distinguish coincident sources:                                                                                                              
+// XXX these are similar (identical?) to the goals of pmSourceMatch.c                                                                                                            
+                                                                                                                                                                                 
+# define NEXT1 { i++; continue; }                                                                                                                                                
+# define NEXT2 { j++; continue; }                                                                                                                                                
+                                                                                                                                                                                 
+psphotSourceMerge () {                                                                                                                                                           
+                                                                                                                                                                                 
+    float dx, dy;                                                                                                                                                                
+                                                                                                                                                                                 
+    // sort the source list by X                                                                                                                                                 
+    pmSourceSortByX (sources1);                                                                                                                                                  
+    pmSourceSortByX (sources2);                                                                                                                                                  
+                                                                                                                                                                                 
+    int i, j;                                                                                                                                                                    
+    for (i = j = 0; (i < sources1->n) && (j < sources2->n); ) {                                                                                                                  
+                                                                                                                                                                                 
+        pmSource *src1 = sources1->data[i];                                                                                                                                      
+        pmSource *src2 = sources2->data[j];                                                                                                                                      
+                                                                                                                                                                                 
+        if (!src1) NEXT1;                                                                                                                                                        
+        if (!src1->peak) NEXT1;                                                                                                                                                  
+        if (!finite(src1->peak->xf)) NEXT1;                                                                                                                                      
+        if (!finite(src1->peak->yf)) NEXT1;                                                                                                                                      
+                                                                                                                                                                                 
+        if (!src2) NEXT2;                                                                                                                                                        
+        if (!src2->peak) NEXT2;                                                                                                                                                  
+        if (!finite(src2->peak->xf)) NEXT2;                                                                                                                                      
+        if (!finite(src2->peak->yf)) NEXT2;                                                                                                                                      
+                                                                                                                                                                                 
+        dx = src1->peak->xf - src2->peak->xf;                                                                                                                                    
+        if (dx < -1.02*RADIUS) NEXT1;                                                                                                                                            
+        if (dx > +1.02*RADIUS) NEXT2;                                                                                                                                            
+                                                                                                                                                                                 
+        // we are within match range, look for matches:                                                                                                                          
+        for (int J = j; (dx > -1.02*radius) && (J < sources2->n); J++) {                                                                                                         
+                                                                                                                                                                                 
+            dx = src1->peak->xf - src2->peak->xf;                                                                                                                                
+            dy = src1->peak->yf - src2->peak->yf;                                                                                                                                
+                                                                                                                                                                                 
+            dr = dx*dx + dy*dy;                                                                                                                                                  
+            if (dr > RADIUS2) continue;                                                                                                                                          
+                                                                                                                                                                                 
+            // add to group?                                                                                                                                                     
+        }                                                                                                                                                                        
+        i++;                                                                                                                                                                     
+    }                                                                                                                                                                            
+}      
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceMerge.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceMerge.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceMerge.c	(revision 27622)
@@ -0,0 +1,49 @@
+
+// we need a couple of functions to distinguish coincident sources:
+// XXX these are similar (identical?) to the goals of pmSourceMatch.c
+
+# define NEXT1 { i++; continue; }
+# define NEXT2 { j++; continue; }
+
+psphotSourceMerge () {
+
+    float dx, dy;
+
+    // sort the source list by X
+    pmSourceSortByX (sources1);
+    pmSourceSortByX (sources2);
+
+    int i, j;
+    for (i = j = 0; (i < sources1->n) && (j < sources2->n); ) {
+
+	pmSource *src1 = sources1->data[i];
+	pmSource *src2 = sources2->data[j];
+
+	if (!src1) NEXT1;
+	if (!src1->peak) NEXT1;
+	if (!finite(src1->peak->xf)) NEXT1;
+	if (!finite(src1->peak->yf)) NEXT1;
+
+	if (!src2) NEXT2;
+	if (!src2->peak) NEXT2;
+	if (!finite(src2->peak->xf)) NEXT2;
+	if (!finite(src2->peak->yf)) NEXT2;
+
+	dx = src1->peak->xf - src2->peak->xf;
+	if (dx < -1.02*RADIUS) NEXT1;
+	if (dx > +1.02*RADIUS) NEXT2;
+
+	// we are within match range, look for matches:
+	for (int J = j; (dx > -1.02*radius) && (J < sources2->n); J++) {
+
+	    dx = src1->peak->xf - src2->peak->xf;
+	    dy = src1->peak->yf - src2->peak->yf;
+
+	    dr = dx*dx + dy*dy;
+	    if (dr > RADIUS2) continue;
+
+	    // add to group?
+	}
+	i++;
+    }
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourcePlots.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourcePlots.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourcePlots.c	(revision 27622)
@@ -0,0 +1,167 @@
+# include "psphotInternal.h"
+
+bool psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status = false;
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 21;
+    int DY = 21;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;                         // starting corner of next box
+    int dY = 0;                         // height of row so far
+    int NX = 20*DX;                     // full width of output image
+    int NY = DY;                        // total height of output image so far
+
+    // first, examine the PSF and SAT stars to set output image size:
+    // - add stamp widths until we exceed output image width,
+    // - then start a new row offset by max height
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        keep |= (source->mode & PM_SOURCE_MODE_PSFSTAR);
+        keep |= (source->mode & PM_SOURCE_MODE_SATSTAR);
+        if (!keep) {
+	    psTrace ("psphot", 4, "not plotting star %d: %x", i, source->mode);
+	    continue;
+	}
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (dX + DX > NX) {
+            // too wide for the rest of this row
+            if (dX == 0) {
+                // alone on this row
+                NY += DY;
+                dX = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                NY += dY;
+                dX = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            dX += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    if (NY == 0) {
+	psWarning ("no PSF or SAT stars to plot? skipping.\n");
+	return false;
+    }
+
+    // allocate output image
+    psImage *outpos = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImage *outsub = psImageAlloc (NX, NY, PS_TYPE_F32);
+
+    psImageInit (outpos, 0.0);
+    psImageInit (outsub, 0.0);
+
+    int Xo = 0;                         // starting corner of next box
+    int Yo = 0;                         // starting corner of next box
+    dY = 0;                             // height of row so far
+
+    int nPSF = 0;
+    int nSAT = 0;
+    int kapa = 0;                       // file descriptor for plotting routine
+
+    // first, examine the PSF and SAT stars:
+    // - generate radial plots (PS plots)
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+            nPSF ++;
+            keep = true;
+        }
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+            nSAT ++;
+            keep = true;
+        }
+        if (!keep) continue;
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (Xo + DX > NX) {
+            // too wide for the rest of this row
+            if (Xo == 0) {
+                // place source alone on this row
+		bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+		if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+                psphotRadialPlot (&kapa, "radial.plots.ps", source);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+		pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+
+		if (!subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+                Yo += DY;
+                Xo = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                Yo += dY;
+                Xo = 0;
+
+		bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+		if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+                psphotRadialPlot (&kapa, "radial.plots.ps", source);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+		pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+		if (!subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+                Xo = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+	    bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+	    if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+            psphotRadialPlot (&kapa, "radial.plots.ps", source);
+            psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+	    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+            psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+	    if (!subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+            Xo += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    psphotSaveImage (NULL, outpos, "outpos.fits");
+    psphotSaveImage (NULL, outsub, "outsub.fits");
+    psLogMsg ("psphot", PS_LOG_INFO, "plotted %d sources (%d psf, %d sat): %f sec\n", nPSF + nSAT, nPSF, nSAT, psTimerMark ("psphot"));
+
+    psFree (outpos);
+    psFree (outsub);
+    return true;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceSize.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceSize.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceSize.c	(revision 27622)
@@ -0,0 +1,1183 @@
+# include "psphotInternal.h"
+# include <gsl/gsl_sf_gamma.h>
+
+typedef struct {
+    psImageMaskType maskVal;
+    psImageMaskType markVal;
+    psImageMaskType crMask;
+    float ApResid;
+    float ApSysErr;
+    float nSigmaApResid;
+    float nSigmaMoments;
+    float nSigmaCR;
+    float soft;
+    int grow;
+    int xtest, ytest;
+    bool apply; // apply CR mask?
+} psphotSourceSizeOptions;
+
+// local functions:
+bool psphotSourceSizePSF (psphotSourceSizeOptions *options, psArray *sources, pmPSF *psf);
+bool psphotSourceClass (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options);
+bool psphotSourceClassRegion (psRegion *region, pmPSFClump *psfClump, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options);
+bool psphotSourceSizeCR (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options);
+bool psphotMaskCosmicRay (pmReadout *readout, pmSource *source, psImageMaskType maskVal);
+bool psphotMaskCosmicRayFootprintCheck (psArray *sources);
+int  psphotMaskCosmicRayConnected (int xPeak, int yPeak, psImage *mymask, psImage *myvar, psImage *edges, int binning, float sigma_thresh);
+
+// we need to call this function after sources have been fitted to the PSF model and
+// subtracted.  To determine the CR-nature, this function examines the 9 pixels in the 3x3
+// square containing the peak and compares the observed flux to the model.  To determine
+// the EXT-nature, this function measures the amount of positive or negative total
+// deviation from the psf model at the r = FWHM/2 position
+
+// for now, let's store the detections on the readout->analysis for each readout
+bool psphotSourceSize (pmConfig *config, const pmFPAview *view, bool getPSFsize)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSourceSizeReadout (config, view, "PSPHOT.INPUT", i, recipe, getPSFsize)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed on source size analysis for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
+
+// this function use an internal flag to mark sources which have already been measured
+bool psphotSourceSizeReadout(pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool getPSFsize)
+{
+    bool status;
+    psphotSourceSizeOptions options;
+
+    psTimerStart ("psphot.size");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+
+    psArray *sources = detections->allSources;
+    psAssert (sources, "missing sources?");
+
+    if (!sources->n) {
+	psLogMsg ("psphot", PS_LOG_INFO, "no sources, skipping source size");
+	return true;
+    }
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    psAssert (psf, "missing psf?");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    options.maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (options.maskVal);
+
+    options.markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT"); // Mask value for bad pixels
+    assert (options.markVal);
+
+    // bit to mask the cosmic-ray pixels
+    options.crMask  = pmConfigMaskGet("CR", config); // Mask value for cosmic rays
+
+    options.nSigmaCR = psMetadataLookupF32 (&status, recipe, "PSPHOT.CR.NSIGMA.LIMIT");
+    assert (status);
+
+    // XXX recipe name is not great
+    options.nSigmaApResid = psMetadataLookupF32 (&status, recipe, "PSPHOT.EXT.NSIGMA.LIMIT");
+    assert (status);
+
+    // XXX recipe name is not great
+    options.nSigmaMoments = psMetadataLookupF32 (&status, recipe, "PSPHOT.EXT.NSIGMA.MOMENTS");
+    assert (status);
+
+    // XXX recipe name is not great
+    options.xtest = psMetadataLookupS32 (&status, recipe, "PSPHOT.CRMASK.XTEST");
+    options.ytest = psMetadataLookupS32 (&status, recipe, "PSPHOT.CRMASK.YTEST");
+    assert (status);
+
+    options.grow = psMetadataLookupS32(&status, recipe, "PSPHOT.CR.GROW"); // Growth size for CRs
+    if (!status || options.grow < 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PSPHOT.CR.GROW is not positive.");
+        return false;
+    }
+
+    options.soft = psMetadataLookupF32(&status, recipe, "PSPHOT.CR.NSIGMA.SOFTEN"); // Softening parameter
+    if (!status || !isfinite(options.soft) || options.soft < 0.0) {
+        psWarning("PSPHOT.CR.NSIGMA.SOFTEN not set; defaulting to zero.");
+        options.soft = 0.0;
+    }
+
+    options.apply = psMetadataLookupBool(&status, recipe, "PSPHOT.CRMASK.APPLY"); // Growth size for CRs
+    if (!status) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PSPHOT.CRMASK.APPLY is not defined.");
+        return false;
+    }
+
+    // We are using the value psfMag - 2.5*log10(moment.Sum) as a measure of the extendedness
+    // of an object.  We need to model this distribution for the PSF stars before we can test
+    // the significance for a specific object
+    // XXX move this to the code that generates the PSF?
+    // XXX store the results on pmPSF?
+
+    // XXX this should only be done on the first pass (ie, if we have newSources or allSources?)
+    if (getPSFsize) {
+	psphotSourceSizePSF (&options, sources, psf);
+    }
+
+    // classify the sources based on ApResid and Moments (extended sources)
+    // NOTE: only sources not already measured !(source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED)
+    psphotSourceClass(readout, sources, recipe, psf, &options);
+
+    // NOTE: only sources not already measured !(source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED)
+    psphotSourceSizeCR (readout, sources, &options);
+
+    // XXX fix this (was source->n  - first)
+    psLogMsg ("psphot.size", PS_LOG_INFO, "measure source sizes for %ld sources: %f sec\n", sources->n, psTimerMark ("psphot.size"));
+
+    psphotVisualPlotSourceSize (recipe, readout->analysis, sources);
+    psphotVisualShowSourceSize (readout, sources);
+    psphotVisualPlotApResid (sources, options.ApResid, options.ApSysErr);
+    psphotVisualShowSatStars (recipe, psf, sources);
+
+    return true;
+}
+
+// model the apmifit distribution for the psf stars:
+bool psphotSourceSizePSF (psphotSourceSizeOptions *options, psArray *sources, pmPSF *psf) {
+
+    // select stats from the psf stars
+    psVector *Ap = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *ApErr = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    psImageMaskType maskVal = options->maskVal | options->markVal;
+
+    // XXX  why PHOT_WEIGHT??
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_WEIGHT;
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, options->maskVal);
+        }
+
+        // clear the mask bit and set the circular mask pixels
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", options->markVal);
+
+        // XXX can we test if psfMag is set and calculate only if needed?
+        pmSourceMagnitudes (source, psf, photMode, maskVal); // maskVal includes markVal
+
+        // clear the mask bit
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
+
+        float apMag = -2.5*log10(source->moments->Sum);
+        float dMag = source->psfMag - apMag;
+
+        psVectorAppend (Ap, dMag);
+        psVectorAppend (ApErr, source->errMag);
+    }
+
+    // model the distribution as a mean or median value and a systematic error from that value:
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    psVectorStats (stats, Ap, NULL, NULL, 0);
+
+    psVector *dAp = psVectorAlloc (Ap->n, PS_TYPE_F32);
+    for (int i = 0; i < Ap->n; i++) {
+        dAp->data.F32[i] = Ap->data.F32[i] - stats->robustMedian;
+    }
+
+    options->ApResid = stats->robustMedian;
+    options->ApSysErr = psVectorSystematicError(dAp, ApErr, 0.05);
+    // XXX this is quite arbitrary...
+    if (!isfinite(options->ApSysErr)) options->ApSysErr = 0.01;
+    psLogMsg ("psphot", PS_LOG_DETAIL, "psf - Sum: %f +/- %f\n", options->ApResid, options->ApSysErr);
+
+    psFree (Ap);
+    psFree (ApErr);
+    psFree (stats);
+    psFree (dAp);
+
+    return true;
+}
+
+// classify sources based on the combination of psf-mag, Mxx, Myy
+bool psphotSourceClass (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options) {
+
+    bool status;
+    pmPSFClump psfClump;
+    char regionName[64];
+
+    psLogMsg("psModules.objects", PS_LOG_INFO, "Source Size classifications: %4s %4s %4s %4s %4s %4s", "Npsf", "Next", "Nsat", "Ncr", "Nmiss", "Nskip");
+
+    int nRegions = psMetadataLookupS32 (&status, readout->analysis, "PSF.CLUMP.NREGIONS");
+    for (int i = 0; i < nRegions; i ++) {
+        snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", i);
+        psMetadata *regionMD = psMetadataLookupPtr (&status, readout->analysis, regionName);
+        psAssert (regionMD, "regions must be defined by earlier call to psphotRoughClassRegion");
+
+        psRegion *region = psMetadataLookupPtr (&status, regionMD, "REGION");
+        psAssert (region, "regions must be defined by earlier call to psphotRoughClassRegion");
+
+        // pull FWHM_X,Y from the recipe, use to define psfClump.X,Y
+        psfClump.X  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");   psAssert (status, "missing PSF.CLUMP.X");
+        psfClump.Y  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");   psAssert (status, "missing PSF.CLUMP.Y");
+        psfClump.dX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");  psAssert (status, "missing PSF.CLUMP.DX");
+        psfClump.dY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");  psAssert (status, "missing PSF.CLUMP.DY");
+
+        if ((psfClump.X < 0) || (psfClump.Y < 0) || !psfClump.X || !psfClump.Y || isnan(psfClump.X) || isnan(psfClump.Y)) {
+            psLogMsg ("psphot", 4, "Failed to find a valid PSF clump for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
+            continue;
+        }
+
+        if (!psphotSourceClassRegion (region, &psfClump, sources, recipe, psf, options)) {
+            psLogMsg ("psphot", 4, "Failed to determine source classification for region %f,%f - %f,%f\n", region->x0, region->y0, region->x1, region->y1);
+            continue;
+        }
+	// psphotVisualPlotSourceSize (recipe, readout->analysis, sources);
+    }
+
+    return true;
+}
+
+# define SIZE_SN_LIM 10
+bool psphotSourceClassRegion (psRegion *region, pmPSFClump *psfClump, psArray *sources, psMetadata *recipe, pmPSF *psf, psphotSourceSizeOptions *options) {
+
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+
+    int Nsat  = 0;
+    int Next  = 0;
+    int Npsf  = 0;
+    int Ncr   = 0;
+    int Nmiss = 0;
+    int Nskip = 0;
+
+    pmSourceMode noMoments = PM_SOURCE_MODE_MOMENTS_FAILURE | PM_SOURCE_MODE_SKYVAR_FAILURE | PM_SOURCE_MODE_SKY_FAILURE | PM_SOURCE_MODE_BELOW_MOMENTS_SN;
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_WEIGHT;
+
+    psImageMaskType maskVal = options->maskVal | options->markVal;
+
+    for (psS32 i = 0 ; i < sources->n ; i++) {
+
+        pmSource *source = (pmSource *) sources->data[i];
+
+        // psfClumps are found for image subregions:
+        // skip sources not in this region
+        if (source->peak->x <  region->x0) continue;
+        if (source->peak->x >= region->x1) continue;
+        if (source->peak->y <  region->y0) continue;
+        if (source->peak->y >= region->y1) continue;
+
+        // skip source if it was already measured
+        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
+            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
+            continue;
+        }
+
+        // source must have been subtracted
+        if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) {
+            source->mode |= PM_SOURCE_MODE_SIZE_SKIPPED;
+            psTrace("psphot", 7, "Not calculating source size since source is not subtracted\n");
+            continue;
+        }
+
+        // we are basically classifying by moments
+        psAssert (source->moments, "why is this source missing moments?");
+        if (source->mode & noMoments) {
+            Nskip ++;
+            continue;
+        }
+
+	// convert to Mmaj, Mmin:
+        psF32 Mxx = source->moments->Mxx;
+        psF32 Myy = source->moments->Myy;
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, options->maskVal);
+        }
+
+        // clear the mask bit and set the circular mask pixels
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, source->apRadius, "OR", options->markVal);
+
+        // XXX can we test if psfMag is set and calculate only if needed?
+        pmSourceMagnitudes (source, psf, photMode, maskVal); // maskVal includes markVal
+
+        // clear the mask bit
+        psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(options->markVal));
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
+
+        float apMag = -2.5*log10(source->moments->Sum);
+        float dMag = source->psfMag - apMag;
+
+	// set nSigma to include both systematic and poisson error terms
+	// XXX the 'poisson error' contribution for size is probably wrong...
+        float nSigmaMAG = (dMag - options->ApResid) / hypot(source->errMag, options->ApSysErr);
+	float nSigmaMXX = (Mxx - psfClump->X) / hypot(psfClump->dX, psfClump->X*psfClump->X*source->errMag);
+	float nSigmaMYY = (Myy - psfClump->Y) / hypot(psfClump->dY, psfClump->Y*psfClump->Y*source->errMag);
+
+	// partially-masked sources are more likely to be mis-measured PSFs
+	float sizeBias = 1.0;
+	if (source->pixWeight < 0.9) {
+	    sizeBias = 3.0;
+	} 
+
+	float minMxx = psfClump->X - sizeBias*options->nSigmaMoments*psfClump->dX;
+	float minMyy = psfClump->Y - sizeBias*options->nSigmaMoments*psfClump->dY;
+
+	// include MAG, MXX, and MYY?
+        source->extNsigma = nSigmaMAG;
+
+	// notes to clarify the source size classification rules:
+	// * a defect should be functionally equivalent to a cosmic ray
+	// * CR & defect should have a faintess limit (min S/N)
+	// * SAT stars should not be faint, but defects may?
+
+        // Anything within this region is a probably PSF-like object. Saturated stars may land
+        // in this region, but are detected elsewhere on the basis of their peak value.
+        bool isPSF = (fabs(nSigmaMAG) < options->nSigmaApResid) && (fabs(nSigmaMXX) < sizeBias*options->nSigmaMoments) && (fabs(nSigmaMYY) < sizeBias*options->nSigmaMoments);
+        if (isPSF) {
+	  psTrace("psphotSourceClassRegion.PSF",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g PSF\t%g %g\n",
+		  source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigmaMAG,
+		  options->nSigmaApResid,sizeBias*options->nSigmaMoments);
+	  source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+	  Npsf ++;
+	  continue;
+        }
+
+        // Defects may not always match CRs from peak curvature analysis
+        // Defects may also be marked as SATSTAR -- XXX deactivate this flag?
+        // XXX this rule is not great
+	// XXX only accept brightish detections as CRs
+	// (nSigmaMAG < -options->nSigmaApResid) || 
+	bool isCR = isCR = (source->errMag < 1.0 / SIZE_SN_LIM) && ((Mxx < minMxx) || (Myy < minMyy));
+	if (isCR) {
+	    psTrace("psphotSourceClassRegion.CR",4,"CLASS: %g %g %f\t%g %g  %g %g  %g %g\t%g %g\t%g CR\t%g %g\n",
+		    source->peak->xf,source->peak->yf,source->pixWeight,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigmaMAG,
+		    options->nSigmaApResid,sizeBias*options->nSigmaMoments);
+            source->mode |= PM_SOURCE_MODE_DEFECT;
+	    source->tmpFlags |= PM_SOURCE_TMPF_SIZE_CR_CANDIDATE;
+            Ncr ++;
+            continue;
+        }
+
+        // saturated star (determined in PSF fit).  These may also be saturated galaxies, or
+        // just large saturated regions.
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace("psphotSourceClassRegion.SAT",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g SAT\t%g %g\n",
+		    source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigmaMAG,
+		    options->nSigmaApResid,sizeBias*options->nSigmaMoments);
+	    source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+            Nsat ++;
+            continue;
+        }
+
+        // XXX allow the Mxx, Myy to be less than psfClump->X,Y (by some nSigma)?
+        bool isEXT = (nSigmaMAG > options->nSigmaApResid) || (Mxx > minMxx) || (Myy > minMyy);
+        if (isEXT) {
+	  psTrace("psphotSourceClassRegion.EXT",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g Ext\t%g %g\n",
+		  source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigmaMAG,
+		  options->nSigmaApResid,sizeBias*options->nSigmaMoments);
+	  
+            source->mode |= PM_SOURCE_MODE_EXT_LIMIT;
+	    source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+            Next ++;
+            continue;
+        }
+	psTrace("psphotSourceClassRegion.MISS",4,"CLASS: %g %g\t%g %g  %g %g  %g %g\t%g %g\t%g Unk\t%g %g\n",
+		source->peak->xf,source->peak->yf,Mxx,Myy,psfClump->X,psfClump->Y,psfClump->dX,psfClump->dY,apMag,dMag,nSigmaMAG,
+		options->nSigmaApResid,sizeBias*options->nSigmaMoments);
+	
+	// sources that reach here are probably too faint for a reasonable source size measurement
+        // psWarning ("sourse size was missed for %f,%f : %f %f -- %f\n", source->peak->xf, source->peak->yf, Mxx, Myy, nSigmaMAG);
+	source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+        Nmiss ++;
+    }
+
+    psLogMsg("psModules.objects", PS_LOG_INFO, "Source Size classifications: %4d %4d %4d %4d %4d %4d", Npsf, Next, Nsat, Ncr, Nmiss, Nskip);
+
+    return true;
+}
+
+// given an object suspected to be a defect, generate a pixel mask using the Lapacian transform
+// if enough of the object is detected as 'sharp', consider the object a cosmic ray
+bool psphotSourceSizeCR (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options) {
+
+    psTimerStart ("psphot.cr");
+
+    int nMasked = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip source if it was already measured
+        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
+            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
+            continue;
+        }
+
+        // only check candidates marked above
+        if (!(source->tmpFlags & PM_SOURCE_TMPF_SIZE_CR_CANDIDATE)) {
+            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
+            continue;
+        }
+
+        // skip unless this source is thought to be a cosmic ray.  flag the detection and mask the pixels
+	// XXX this may be degenerate with the above test
+	if (!(source->mode & PM_SOURCE_MODE_DEFECT)) continue;
+
+        // Integer position of peak
+        int xPeak = source->peak->xf - source->pixels->col0 + 0.5;
+        int yPeak = source->peak->yf - source->pixels->row0 + 0.5;
+
+        // Skip sources which are too close to a boundary.  These are mostly caught as DEFECT
+        if (xPeak < 1 || xPeak > source->pixels->numCols - 2 ||
+            yPeak < 1 || yPeak > source->pixels->numRows - 2) {
+            psTrace("psphot", 7, "Not calculating crNsigma due to edge\n");
+            continue;
+        }
+	
+	// XXX for testing, only CRMASK a single source:
+	if (options->xtest && (fabs(source->peak->xf - options->xtest) > 5)) continue;
+	if (options->ytest && (fabs(source->peak->yf - options->ytest) > 5)) continue;
+
+        // replace object in image
+        if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, options->maskVal);
+        }
+
+	// XXX this is running slowly and is too agressive, but it more-or-less works
+	psTrace("psphot", 6, "mask cosmic ray at %f, %f\n", source->peak->xf, source->peak->yf);
+	if (options->apply) {
+	    psphotMaskCosmicRay(readout, source, options->crMask);
+	} else {
+	    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+	}
+	nMasked ++;
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, options->maskVal);
+    }
+    
+    // now that we have masked pixels associated with CRs, we can grow the mask
+    if (options->grow > 0) {
+        bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading for psImageConvolveMask
+        psImage *newMask = psImageConvolveMask(NULL, readout->mask, options->crMask, options->crMask, -options->grow, options->grow, -options->grow, options->grow);
+        psImageConvolveSetThreads(oldThreads);
+        if (!newMask) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to grow CR mask");
+            return false;
+        }
+        psFree(readout->mask);
+        readout->mask = newMask;
+    }
+
+    psLogMsg ("psphot.cr", PS_LOG_INFO, "mask CR: %d masked in %f sec\n", nMasked, psTimerMark ("psphot.cr"));
+
+    // XXX test : save the mask image
+    if (0) {
+	psphotSaveImage (NULL, readout->mask,   "mask.fits");
+    }
+
+    return true;
+}
+
+# define DUMPPICS 0
+# define LIMIT_XRANGE(X, IMAGE) { X = PS_MIN(PS_MAX(0, X), IMAGE->numCols); }
+# define LIMIT_YRANGE(Y, IMAGE) { Y = PS_MIN(PS_MAX(0, Y), IMAGE->numRows); }
+
+// Comments by CZW 20091209 : Mechanics of how to identify CR pixels taken from "Cosmic-Ray
+// Rejection by Laplacian Edge Detection" by Pieter van Dokkum, arXiv:astro-ph/0108003.  This
+// does no repair or recovery of the CR pixels, it only masks them out.  My test code can be
+// found at /data/ipp031.0/watersc1/psphot.20091209/algo_check.c
+bool psphotMaskCosmicRay (pmReadout *readout, pmSource *source, psImageMaskType maskVal) {
+
+    // Get the actual images and information about the peak.
+    psImage *mask = readout->mask;
+    pmPeak *peak = source->peak;
+    pmFootprint *footprint = peak->footprint;
+
+    // Bounding boxes are inclusive of final pixel
+    int xs = footprint->bbox.x0;
+    int xe = footprint->bbox.x1 + 1;
+    int ys = footprint->bbox.y0;
+    int ye = footprint->bbox.y1 + 1;
+
+    LIMIT_XRANGE(xs, mask);
+    LIMIT_XRANGE(xe, mask);
+    LIMIT_YRANGE(ys, mask);
+    LIMIT_YRANGE(ye, mask);
+
+    int dx = xe - xs;
+    int dy = ye - ys;
+
+    psImage *image= readout->image;
+    psImage *variance = readout->variance;
+      
+    int binning = 2;
+    float sigma_thresh = 3.0;
+    int max_iter = 1; // XXX with isophot masking, we only want to do a single pass
+
+    // Temporary images.
+    psImage *mypix  = psImageAlloc(dx,dy,image->type.type);
+    psImage *myfix  = psImageAlloc(dx,dy,image->type.type);
+    psImage *myvar  = psImageAlloc(dx,dy,image->type.type);
+    psImage *binned = psImageAlloc(dx * binning,dy * binning,image->type.type);
+    psImage *conved = psImageAlloc(dx * binning,dy * binning,image->type.type);
+    psImage *edges  = psImageAlloc(dx,dy,image->type.type);
+    psImage *mymask = psImageAlloc(dx,dy,PS_TYPE_IMAGE_MASK);
+      
+    // Load my copy of things.
+    for (int y = 0; y < dy; y++) {
+	for (int x = 0; x < dx; x++) {
+	    mypix->data.F32[y][x] = image->data.F32[y+ys][x+xs];
+	    myvar->data.F32[y][x] = variance->data.F32[y+ys][x+xs];
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] = 0x00;
+	}
+    }
+    // Mask so I can see on the output image where the footprint is.
+    for (int i = 0; i < footprint->spans->n; i++) {
+	pmSpan *sp = footprint->spans->data[i];
+	for (int j = sp->x0; j <= sp->x1; j++) {
+	    int y = sp->y - ys;
+	    int x = j - xs;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= 0x01;
+	}
+    }
+
+    int nCRpix = 1; // force at least one pass...
+    for (int iteration = 0; (iteration < max_iter) && (nCRpix > 0); iteration++) {
+	nCRpix = 0;
+	psImageInit (binned, 0.0);
+	psImageInit (conved, 0.0);
+	psImageInit (edges, 0.0);
+
+	// Make subsampled image. Maybe this should be called "unbinned" or something
+	for (int y = 0; y < binning * dy; y++) {
+	    int yraw = y / binning;
+	    for (int x = 0; x < binning * dx; x++) {
+		int xraw = x / binning;
+		binned->data.F32[y][x] = mypix->data.F32[yraw][xraw];
+	    }
+	}
+
+	// Apply Laplace transform (kernel = [[0 -0.25 0][-0.25 1 -0.25][0 -0.25 0]]), clipping at zero
+	for (int y = 1; y < binning * dy - 1; y++) {
+	    for (int x = 1; x < binning * dx - 1; x++) {
+		float value = binned->data.F32[y][x] - 0.25 * 
+		    (binned->data.F32[y+0][x-1] + binned->data.F32[y+0][x+1] + 
+		     binned->data.F32[y-1][x+0] + binned->data.F32[y+1][x+0]);
+		value = PS_MAX(0.0, value);
+
+		conved->data.F32[y][x] = value;
+	    }
+	}
+
+	// Create an edge map by rebinning
+	for (int y = 0; y < binning * dy; y++) {
+	    int yraw = y / binning;
+	    for (int x = 0; x < binning * dx; x++) {
+		int xraw = x / binning;
+		edges->data.F32[yraw][xraw] += conved->data.F32[y][x];
+	    }
+	}
+
+	// coordinate of peak in subimage pixels:
+	int xPeak = peak->x - xs;
+	int yPeak = peak->y - ys;
+
+	// Modify my mask if we're above the significance threshold, but only for connected pixels
+	nCRpix = psphotMaskCosmicRayConnected (xPeak, yPeak, mymask, myvar, edges, binning, sigma_thresh);
+	
+# if DUMPPICS
+	psphotSaveImage (NULL, mypix,   "crmask.pix.fits");
+# endif
+	
+// XXX do not repair the pixels in isophot version
+# if 0
+	// "Repair" Masked pixels for the next round.
+	for (int y = 1; y < dy - 1; y++) {
+	    for (int x = 1; x < dx - 1; x++) {
+		if (!(mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & 0x40)) {
+		    myfix->data.F32[y][x] = mypix->data.F32[y][x];
+		    continue;
+		}
+		myfix->data.F32[y][x] = 0.25 * 
+		    (mypix->data.F32[y+0][x-1] + mypix->data.F32[y+0][x+1] +
+		     mypix->data.F32[y-1][x+0] + mypix->data.F32[y+1][x+0]);
+	    }
+	}
+	
+	// "Repair" Masked pixels for the next round.
+	for (int y = 1; y < dy - 1; y++) {
+	    for (int x = 1; x < dx - 1; x++) {
+		mypix->data.F32[y][x] = myfix->data.F32[y][x];
+	    }
+	}
+# endif
+
+# if DUMPPICS
+	fprintf (stderr, "CRMASK %d %d %d %d %d\n", xs, ys, dx, dy, iteration);
+	psphotSaveImage (NULL, mypix,   "crmask.fix.fits");
+	psphotSaveImage (NULL, myvar,   "crmask.var.fits");
+	psphotSaveImage (NULL, binned,  "crmask.binn.fits");
+	psphotSaveImage (NULL, conved,  "crmask.conv.fits");
+	psphotSaveImage (NULL, edges,   "crmask.edge.fits");
+	psphotSaveImage (NULL, mymask,  "crmask.mask.fits");
+# endif
+	psTrace("psphot.czw",2,"Iter: %d Count: %d",iteration, nCRpix);
+    }
+
+# if 0
+    // A solitary masked pixel is likely a lie. Remove those
+    // XXX can't we use nCRpix == 1 to test for these?
+    for (int x = 0; x < dx; x++) {
+	for (int y = 0; y < dy; y++) {
+	    if (!(mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & 0x40)) continue;
+	    if ((x-1 >= 0) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x-1] & 0x40)) {
+		continue;
+	    }
+	    if ((y-1 >= 0) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y-1][x] & 0x40)) {
+		continue;
+	    }
+	    if ((x+1 < dx) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x+1] & 0x40)) {
+		continue;
+	    }
+	    if ((y+1 < dy) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y+1][x] & 0x40)) {
+		continue;
+	    }
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] ^= 0x40;
+	}
+    }
+# endif
+
+    // transfer temporary mask to real mask & count masked pixels
+    nCRpix = 0;
+    for (int x = 0; x < dx; x++) {
+	for (int y = 0; y < dy; y++) {
+	    if (mymask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & 0x40) {
+		mask->data.PS_TYPE_IMAGE_MASK_DATA[y+ys+mask->row0][x+xs+mask->col0] |= maskVal;
+		nCRpix ++;
+	    }
+	}
+    }
+      
+    // XXX if we decide this REALLY is a cosmic ray, set the CR_LIMIT bit
+    if (nCRpix > 1) {
+	source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+	source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+    }
+    // fprintf (stderr, "CRMASK %d %d %d %d %d\n", peak->x, peak->y, dx, dy, nCRpix);
+
+    psFree(mypix);
+    psFree(myfix);
+    psFree(myvar);
+    psFree(binned);
+    psFree(conved);
+    psFree(edges);
+    psFree(mymask);
+      
+    return true;
+}
+
+bool psphotMaskCosmicRayFootprintCheck (psArray *sources) {
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+	pmPeak *peak = source->peak;
+	pmFootprint *footprint = peak->footprint;
+	if (!footprint) continue;
+	for (int j = 0; j < footprint->spans->n; j++) {
+	    pmSpan *sp = footprint->spans->data[j];
+	    psAssert (sp, "missing span");
+	}
+    }
+    return true;
+}
+
+/**** ------ old versions of cosmic ray masking ----- ****/
+
+bool psphotMaskCosmicRayIsophot (pmSource *source, psImageMaskType maskVal, psImageMaskType crMask);
+
+// This attempt to mask the cosmic rays used the isophotal boundary
+bool psphotMaskCosmicRay_V1 (psImage *mask, pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
+
+    // replace the source flux
+    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+    // flag this as a CR
+    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+    pmPeak *peak = source->peak;
+    psAssert (peak, "NULL peak");
+
+    // grab the matching footprint
+    pmFootprint *footprint = peak->footprint;
+    if (!footprint) {
+      psTrace("psphot.czw",2,"Using isophot CR mask code.");
+      
+        // if we have not footprint, use the old code to mask by isophot
+        psphotMaskCosmicRayIsophot (source, maskVal, crMask);
+        return true;
+    }
+
+    if (!footprint->spans) {
+      psTrace("psphot.czw",2,"Using isophot CR mask code.");
+      
+        // if we have no footprint, use the old code to mask by isophot
+        psphotMaskCosmicRayIsophot (source, maskVal, crMask);
+        return true;
+    }
+    psphotMaskCosmicRayIsophot (source, maskVal, crMask);
+    // mask all of the pixels covered by the spans of the footprint
+    for (int j = 1; j < footprint->spans->n; j++) { 
+	pmSpan *span1 = footprint->spans->data[j]; 
+
+	int iy = span1->y; 
+	int xs = span1->x0; 
+	int xe = span1->x1; 
+
+	for (int ix = xs; ix < xe; ix++) { 
+	    mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask; 
+	} 
+    } 
+    return true;
+}
+
+# define VERBOSE 0
+int psphotMaskCosmicRayConnected (int xPeak, int yPeak, psImage *mymask, psImage *myvar, psImage *edges, int binning, float sigma_thresh) {
+    
+    int xLo, xRo;
+    int nCRpix = 0;
+
+    float noise_factor = 5.0 / 4.0;  // Intrinsic to the Laplacian making noise spikes spikier.
+    
+    // mark the pixels in this row to the left, then the right. stay within footprint
+    int xL = xPeak; // find the range of valid pixels in this row
+    int xR = xPeak;
+    for (int ix = xPeak; (ix >= 0) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[yPeak][ix] & 0x01); ix--) {
+	float noise = binning * sqrt(noise_factor * myvar->data.F32[yPeak][ix]);
+	float value = edges->data.F32[yPeak][ix] / noise;
+	if (value < sigma_thresh ) break;
+	mymask->data.PS_TYPE_IMAGE_MASK_DATA[yPeak][ix] |= 0x40;
+	xL = ix;
+	nCRpix ++;
+	if (VERBOSE) fprintf (stderr, "mark %d,%d (%d) : %d - %d\n", ix, yPeak, nCRpix, xL, xR);
+    }
+    for (int ix = xPeak; (ix < mymask->numCols) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[yPeak][ix] & 0x01); ix++) {
+	float noise = binning * sqrt(noise_factor * myvar->data.F32[yPeak][ix]);
+	float value = edges->data.F32[yPeak][ix] / noise;
+	if (value < sigma_thresh ) break;
+	mymask->data.PS_TYPE_IMAGE_MASK_DATA[yPeak][ix] |= 0x40;
+	xR = ix;
+	nCRpix ++;
+	if (VERBOSE) fprintf (stderr, "mark %d,%d (%d) : %d - %d\n", ix, yPeak, nCRpix, xL, xR);
+    }
+    // xL and xR mark the first and last valid pixel in the row
+
+    // for each of the neighboring rows, mark the high pixels if they touch the range xL to xR 
+    xLo = PS_MAX(xL - 1, 0);
+    xRo = PS_MIN(xR + 1, mymask->numCols);
+
+    // first go down:
+    for (int iy = yPeak - 1; iy >= 0; iy--) {
+
+	int xLn = -1;
+	int xRn = -1;
+	int newPix = 0;
+
+	// mark the pixels in the good range
+	for (int ix = xLo; ix < xRo; ix++) {
+	    if (!(mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & 0x01)) continue; // only use pixels in the footprint
+ 	    float noise = binning * sqrt(noise_factor * myvar->data.F32[iy][ix]);
+	    float value = edges->data.F32[iy][ix] / noise;
+	    if (value < sigma_thresh ) continue;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= 0x40;
+	    if (xLn == -1) xLn = ix; // first valid pixel in this row
+	    xRn = ix;		     // last valid pixel in this row
+	    nCRpix ++;
+	    newPix ++;
+	    if (VERBOSE) fprintf (stderr, "mark C %d,%d (%d) : %d - %d | %d - %d | %d - %d \n", ix, iy, nCRpix, xL, xR, xLo, xRo, xLn, xRn);
+	}
+
+	// mark the pixels to the left of the good range
+	for (int ix = xLo; (ix >= 0) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & 0x01); ix--) {
+	    float noise = binning * sqrt(noise_factor * myvar->data.F32[iy][ix]);
+	    float value = edges->data.F32[iy][ix] / noise;
+	    if (value < sigma_thresh ) break;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= 0x40;
+	    if (xRn == -1) xRn = ix; // last valid pixel in this row
+	    xLn = ix;
+	    nCRpix ++;
+	    newPix ++;
+	    if (VERBOSE) fprintf (stderr, "mark L %d,%d (%d) : %d - %d | %d - %d | %d - %d \n", ix, iy, nCRpix, xL, xR, xLo, xRo, xLn, xRn);
+	}
+
+	// mark the pixels to the right of the good range
+	for (int ix = xRo; (ix < mymask->numCols) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & 0x01); ix++) {
+	    float noise = binning * sqrt(noise_factor * myvar->data.F32[iy][ix]);
+	    float value = edges->data.F32[iy][ix] / noise;
+	    if (value < sigma_thresh ) break;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= 0x40;
+	    if (xLn == -1) xLn = ix; // first valid pixel in this row
+	    xRn = ix;
+	    nCRpix ++;
+	    newPix ++;
+	    if (VERBOSE) fprintf (stderr, "mark R %d,%d (%d) : %d - %d | %d - %d | %d - %d \n", ix, iy, nCRpix, xL, xR, xLo, xRo, xLn, xRn);
+	}
+	if (newPix == 0) break;
+	xLo = PS_MAX(xLn - 1, 0);
+	xRo = PS_MIN(xRn + 1, mymask->numCols);
+    }
+
+    xLo = PS_MAX(xL - 1, 0);
+    xRo = PS_MIN(xR + 1, mymask->numCols);
+
+    // next go up:
+    for (int iy = yPeak + 1; iy < mymask->numRows; iy++) {
+
+	int xLn = -1;
+	int xRn = -1;
+	int newPix = 0;
+
+	// mark the pixels in the good range
+	for (int ix = xLo; ix < xRo; ix++) {
+	    if (!(mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & 0x01)) continue; // only use pixels in the footprint
+ 	    float noise = binning * sqrt(noise_factor * myvar->data.F32[iy][ix]);
+	    float value = edges->data.F32[iy][ix] / noise;
+	    if (value < sigma_thresh ) continue;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= 0x40;
+	    if (xLn == -1) xLn = ix; // first valid pixel in this row
+	    xRn = ix;		     // last valid pixel in this row
+	    nCRpix ++;
+	    newPix ++;
+	    if (VERBOSE) fprintf (stderr, "mark C %d,%d (%d) : %d - %d | %d - %d | %d - %d \n", ix, iy, nCRpix, xL, xR, xLo, xRo, xLn, xRn);
+	}
+
+	// mark the pixels to the left of the good range
+	for (int ix = xLo; (ix >= 0) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & 0x01); ix--) {
+	    float noise = binning * sqrt(noise_factor * myvar->data.F32[iy][ix]);
+	    float value = edges->data.F32[iy][ix] / noise;
+	    if (value < sigma_thresh ) break;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= 0x40;
+	    if (xRn == -1) xRn = ix; // last valid pixel in this row
+	    xLn = ix;
+	    nCRpix ++;
+	    newPix ++;
+	    if (VERBOSE) fprintf (stderr, "mark L %d,%d (%d) : %d - %d | %d - %d | %d - %d \n", ix, iy, nCRpix, xL, xR, xLo, xRo, xLn, xRn);
+	}
+
+	// mark the pixels to the right of the good range
+	for (int ix = xRo; (ix < mymask->numCols) && (mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & 0x01); ix++) {
+	    float noise = binning * sqrt(noise_factor * myvar->data.F32[iy][ix]);
+	    float value = edges->data.F32[iy][ix] / noise;
+	    if (value < sigma_thresh ) break;
+	    mymask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= 0x40;
+	    if (xLn == -1) xLn = ix; // first valid pixel in this row
+	    xRn = ix;
+	    nCRpix ++;
+	    newPix ++;
+	    if (VERBOSE) fprintf (stderr, "mark R %d,%d (%d) : %d - %d | %d - %d | %d - %d \n", ix, iy, nCRpix, xL, xR, xLo, xRo, xLn, xRn);
+	}
+	if (newPix == 0) break;
+	xLo = PS_MAX(xLn - 1, 0);
+	xRo = PS_MIN(xRn + 1, mymask->numCols);
+    }
+
+    return nCRpix;
+}
+
+bool psphotMaskCosmicRayIsophot (pmSource *source, psImageMaskType maskVal, psImageMaskType crMask) {
+
+    source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+    pmPeak *peak = source->peak;
+    psAssert (peak, "NULL peak");
+
+    psImage *mask   = source->maskView;
+    psImage *pixels = source->pixels;
+    psImage *variance = source->variance;
+
+    // XXX This should be a recipe variable
+# define SN_LIMIT 5.0
+
+    int xo = peak->x - pixels->col0;
+    int yo = peak->y - pixels->row0;
+
+    // mark the pixels in this row to the left, then the right
+    for (int ix = xo; ix >= 0; ix--) {
+        float SN = pixels->data.F32[yo][ix] / sqrt(variance->data.F32[yo][ix]);
+        if (SN > SN_LIMIT) {
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[yo][ix] |= crMask;
+        }
+    }
+    for (int ix = xo + 1; ix < pixels->numCols; ix++) {
+        float SN = pixels->data.F32[yo][ix] / sqrt(variance->data.F32[yo][ix]);
+        if (SN > SN_LIMIT) {
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[yo][ix] |= crMask;
+        }
+    }
+
+    // for each of the neighboring rows, mark the high pixels if they have a marked neighbor
+    // first go up:
+    for (int iy = PS_MIN(yo, mask->numRows-2); iy >= 0; iy--) {
+        // mark the pixels in this row to the left, then the right
+        for (int ix = 0; ix < pixels->numCols; ix++) {
+            float SN = pixels->data.F32[iy][ix] / sqrt(variance->data.F32[iy][ix]);
+            if (SN < SN_LIMIT) continue;
+
+            bool valid = false;
+            valid |= (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix] & crMask);
+            valid |= (ix > 0) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix-1] & crMask) : 0;
+            valid |= (ix <= mask->numCols) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy+1][ix+1] & crMask) : 0;
+
+            if (!valid) continue;
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
+        }
+    }
+    // next go down:
+    for (int iy = PS_MIN(yo+1, mask->numRows-1); iy < pixels->numRows; iy++) {
+        // mark the pixels in this row to the left, then the right
+        for (int ix = 0; ix < pixels->numCols; ix++) {
+            float SN = pixels->data.F32[iy][ix] / sqrt(variance->data.F32[iy][ix]);
+            if (SN < SN_LIMIT) continue;
+
+            bool valid = false;
+            valid |= (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix] & crMask);
+            valid |= (ix > 0) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix-1] & crMask) : 0;
+            valid |= (ix <= mask->numCols) ? (mask->data.PS_TYPE_IMAGE_MASK_DATA[iy-1][ix+1] & crMask) : 0;
+
+            if (!valid) continue;
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= crMask;
+        }
+    }
+    return true;
+}
+
+// given the PSF ellipse parameters, navigate around the 1sigma contour, return the total
+// deviation in sigmas.  This is measured on the residual image - should we ignore negative
+// deviations?  NOTE: This function was an early attempt to classify extended objects, and is
+// no longer used by psphot.
+float psphotModelContour(const psImage *image, const psImage *variance, const psImage *mask,
+                         psImageMaskType maskVal, const pmModel *model, float Ro)
+{
+    psF32 *PAR = model->params->data.F32; // Model parameters
+    float sxx = PAR[PM_PAR_SXX], sxy = PAR[PM_PAR_SXY], syy = PAR[PM_PAR_SYY]; // Ellipse parameters
+
+    // We treat the contour as an ellipse:
+    // Ro = (x / SXX)^2 + (y / SYY)^2 + x y SXY
+    // y^2 (1/SYY^2) + y (x SXY) + (x / SXX)^2 - Ro = 0;
+    // This is a quadratic, Ay^2 + By + C with A = 1/SYY^2, B = x*SXY, C = (x / SXX)^2 - Ro
+    // The solution is y = [-B +/- sqrt (B^2 - 4 A C)] / [2 A], so:
+    // y = [-(x SXY) +/- sqrt ((x SXY)^2 - 4 (1/SYY^2) ((x/SXX)^2 - Ro))] * [SYY^2 / 2]
+
+    // min/max value of x is where B^2 - 4AC = 0; solve this for x
+    float Q = Ro * PS_SQR(sxx) / (1.0 - PS_SQR(sxx * syy * sxy) / 4.0);
+    if (Q < 0.0) {
+        // ellipse is imaginary
+        return NAN;
+    }
+
+    int radius = sqrtf(Q) + 0.5;        // Radius of ellipse
+    int nPts = 0;                       // Number of points in ellipse
+    float nSigma = 0.0;                 //
+
+    for (int x = -radius; x <= radius; x++) {
+        // Polynomial coefficients
+        // XXX Should we be using the centre of the pixel as x or x+0.5?
+        float A = PS_SQR (1.0 / syy);
+        float B = x * sxy;
+        float C = PS_SQR (x / sxx) - Ro;
+        float T = PS_SQR(B) - 4*A*C;
+        if (T < 0.0) {
+            continue;
+        }
+
+        // y position in source frame
+        float yP = (-B + sqrt (T)) / (2.0 * A);
+        float yM = (-B - sqrt (T)) / (2.0 * A);
+
+        // Get the closest pixel positions (image frame)
+        int xPix  = x  + PAR[PM_PAR_XPOS] - image->col0 + 0.5;
+        int yPixM = yM + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
+        int yPixP = yP + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
+
+        if (xPix < 0 || xPix >= image->numCols) {
+            continue;
+        }
+
+        if (yPixM >= 0 && yPixM < image->numRows &&
+            !(mask && (mask->data.PS_TYPE_IMAGE_MASK_DATA[yPixM][xPix] & maskVal))) {
+            float dSigma = image->data.F32[yPixM][xPix] / sqrtf(variance->data.F32[yPixM][xPix]);
+            nSigma += dSigma;
+            nPts++;
+        }
+
+        if (yPixM == yPixP) {
+            continue;
+        }
+
+        if (yPixP >= 0 && yPixP < image->numRows &&
+            !(mask && (mask->data.PS_TYPE_IMAGE_MASK_DATA[yPixP][xPix] & maskVal))) {
+            float dSigma = image->data.F32[yPixP][xPix] / sqrtf(variance->data.F32[yPixP][xPix]);
+            nSigma += dSigma;
+            nPts++;
+        }
+    }
+    nSigma /= nPts;
+    return nSigma;
+}
+
+// this was an old attempt to identify cosmic rays based on the peak curvature
+bool psphotSourcePeakCurvature (pmReadout *readout, psArray *sources, psphotSourceSizeOptions *options) {
+
+    // classify the sources based on the CR test (place this in a function?)
+    // XXX use an internal flag to mark sources which have already been measured
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip source if it was already measured
+        if (source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED) {
+            psTrace("psphot", 7, "Not calculating source size since it has already been measured\n");
+            continue;
+        }
+
+        // source must have been subtracted
+        if (!(source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED)) {
+            source->mode |= PM_SOURCE_MODE_SIZE_SKIPPED;
+            psTrace("psphot", 7, "Not calculating source size since source is not subtracted\n");
+            continue;
+        }
+
+        psF32 **resid  = source->pixels->data.F32;
+        psF32 **variance = source->variance->data.F32;
+        psImageMaskType **mask = source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA;
+
+        // Integer position of peak
+        int xPeak = source->peak->xf - source->pixels->col0 + 0.5;
+        int yPeak = source->peak->yf - source->pixels->row0 + 0.5;
+
+        // Skip sources which are too close to a boundary.  These are mostly caught as DEFECT
+        if (xPeak < 1 || xPeak > source->pixels->numCols - 2 ||
+            yPeak < 1 || yPeak > source->pixels->numRows - 2) {
+            psTrace("psphot", 7, "Not calculating crNsigma due to edge\n");
+            continue;
+        }
+
+        // Skip sources with masked pixels.  These are mostly caught as DEFECT
+        bool keep = true;
+        for (int iy = -1; (iy <= +1) && keep; iy++) {
+            for (int ix = -1; (ix <= +1) && keep; ix++) {
+                if (mask[yPeak+iy][xPeak+ix] & options->maskVal) {
+                    keep = false;
+                }
+            }
+        }
+        if (!keep) {
+            psTrace("psphot", 7, "Not calculating crNsigma due to masked pixels\n");
+            continue;
+        }
+
+        // Compare the central pixel with those on either side, for the four possible lines through it.
+
+        // Soften variances (add systematic error)
+        float softening = options->soft * PS_SQR(source->peak->flux); // Softening for variances
+
+        // Across the middle: y = 0
+        float cX = 2*resid[yPeak][xPeak]   - resid[yPeak+0][xPeak-1]  - resid[yPeak+0][xPeak+1];
+        float dcX = 4*variance[yPeak][xPeak] + variance[yPeak+0][xPeak-1] + variance[yPeak+0][xPeak+1];
+        float nX = cX / sqrtf(dcX + softening);
+
+        // Up the centre: x = 0
+        float cY = 2*resid[yPeak][xPeak]   - resid[yPeak-1][xPeak+0]  - resid[yPeak+1][xPeak+0];
+        float dcY = 4*variance[yPeak][xPeak] + variance[yPeak-1][xPeak+0] + variance[yPeak+1][xPeak+0];
+        float nY = cY / sqrtf(dcY + softening);
+
+        // Diagonal: x = y
+        float cL = 2*resid[yPeak][xPeak]   - resid[yPeak-1][xPeak-1]  - resid[yPeak+1][xPeak+1];
+        float dcL = 4*variance[yPeak][xPeak] + variance[yPeak-1][xPeak-1] + variance[yPeak+1][xPeak+1];
+        float nL = cL / sqrtf(dcL + softening);
+
+        // Diagonal: x = - y
+        float cR = 2*resid[yPeak][xPeak]   - resid[yPeak+1][xPeak-1]  - resid[yPeak-1][xPeak+1];
+        float dcR = 4*variance[yPeak][xPeak] + variance[yPeak+1][xPeak-1] + variance[yPeak-1][xPeak+1];
+        float nR = cR / sqrtf(dcR + softening);
+
+        // P(chisq > chisq_obs; Ndof) = gamma_Q (Ndof/2, chisq/2)
+        // Ndof = 4 ? (four measurements, no free parameters)
+        // XXX this value is going to be biased low because of systematic errors.
+        // we need to calibrate it somehow
+        // source->psfProb = gsl_sf_gamma_inc_Q (2, 0.5*chisq);
+
+        // not strictly accurate: overcounts the chisq contribution from the center pixel (by
+        // factor of 4); also biases a bit low if any pixels are masked
+        // XXX I am not sure I want to keep this value...
+        source->psfChisq = PS_SQR(nX) + PS_SQR(nY) + PS_SQR(nL) + PS_SQR(nR);
+
+        float fCR = 0.0;
+        int nCR = 0;
+        if (nX > 0.0) {
+            fCR += nX;
+            nCR ++;
+        }
+        if (nY > 0.0) {
+            fCR += nY;
+            nCR ++;
+        }
+        if (nL > 0.0) {
+            fCR += nL;
+            nCR ++;
+        }
+        if (nR > 0.0) {
+            fCR += nR;
+            nCR ++;
+        }
+        source->crNsigma  = (nCR > 0)  ? fCR / nCR : 0.0;
+        source->tmpFlags |= PM_SOURCE_TMPF_SIZE_MEASURED;
+
+        if (!isfinite(source->crNsigma)) {
+            continue;
+        }
+
+        // this source is thought to be a cosmic ray.  flag the detection and mask the pixels
+        if (source->crNsigma > options->nSigmaCR) {
+            source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+            // XXX still testing... : psphotMaskCosmicRay (readout->mask, source, maskVal, crMask);
+            // XXX acting strange... psphotMaskCosmicRay_Old (source, maskVal, crMask);
+        }
+    }
+
+    // now that we have masked pixels associated with CRs, we can grow the mask
+    if (options->grow > 0) {
+        bool oldThreads = psImageConvolveSetThreads(true); // Old value of threading for psImageConvolveMask
+        psImage *newMask = psImageConvolveMask(NULL, readout->mask, options->crMask, options->crMask, -options->grow, options->grow, -options->grow, options->grow);
+        psImageConvolveSetThreads(oldThreads);
+        if (!newMask) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to grow CR mask");
+            return false;
+        }
+        psFree(readout->mask);
+        readout->mask = newMask;
+    }
+    return true;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceStats.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceStats.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSourceStats.c	(revision 27622)
@@ -0,0 +1,538 @@
+# include "psphotInternal.h"
+
+// convert detections to sources and measure their basic properties (moments, local sky, sky
+// variance) Note: this function only generates sources for the new peaks (peak->assigned)
+bool psphotSourceStats (pmConfig *config, const pmFPAview *view, bool setWindow)
+{
+    bool status = true;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotSourceStatsReadout (config, view, "PSPHOT.INPUT", i, recipe, setWindow)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to find initial detections for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool psphotSourceStatsReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe, bool setWindow) {
+
+    bool status = false;
+    psArray *sources = NULL;
+
+    psTimerStart ("psphot.stats");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (file, "missing file?");
+
+    pmReadout *readout = pmFPAviewThisReadout(view, file->fpa);
+    psAssert (readout, "missing readout?");
+
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    psAssert (detections, "missing detections?");
+    psAssert (!detections->newSources, "new sources already defined?");
+
+    // XXX TEST:
+    if (detections->allSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->allSources);
+    }
+    if (detections->newSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->newSources);
+    }
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // determine properties (sky, moments) of initial sources
+    float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    psAssert (status, "missing SKY_OUTER_RADIUS in recipe?");
+
+    // XXX this seems like an arbitrary number...
+    OUTER = PS_MAX(OUTER, 20.0); // XXX Guarantee that we can encompass the max moments radius
+
+    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
+    psAssert (status, "missing BREAK_POINT?");
+
+    float INNER        = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS"); psAssert (status, "missing SKY_INNER_RADIUS");
+    float MIN_SN       = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN"); psAssert (status, "missing MOMENTS_SN_MIN");
+
+    // bit-masks to test for good/bad pixels
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT");
+    psAssert (maskVal, "missing MASK.PSPHOT");
+
+    // bit-mask to mark pixels not used in analysis
+    psImageMaskType markVal = psMetadataLookupImageMask(&status, recipe, "MARK.PSPHOT");
+    psAssert (markVal, "missing MARK.PSPHOT");
+
+    psArray *peaks = detections->peaks;
+    if (!peaks) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "No peaks found!");
+        return false;
+    }
+
+    // generate the array of sources, define the associated pixel
+    sources = psArrayAllocEmpty (peaks->n);
+
+    // if there are no peaks, we save the empty source array and return
+    if (!peaks->n) {
+        // save the new sources on the detection structure:
+        detections->newSources = sources;
+        return true;
+    }
+
+    for (int i = 0; i < peaks->n; i++) {
+
+        pmPeak *peak = peaks->data[i];
+        if (peak->assigned) continue;
+
+        // create a new source
+        pmSource *source = pmSourceAlloc();
+
+        // add the peak
+        source->peak = psMemIncrRefCounter(peak);
+
+        // allocate space for moments
+        source->moments = pmMomentsAlloc();
+
+        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
+        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+
+        peak->assigned = true;
+        psArrayAdd (sources, 100, source);
+        psFree (source);
+    }
+
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        psLogMsg ("psphot", PS_LOG_INFO, "%ld sources : %f sec\n", sources->n, psTimerMark ("psphot.stats"));
+        psLogMsg ("psphot", PS_LOG_INFO, "break point PEAKS, skipping MOMENTS\n");
+        psphotVisualShowMoments (sources);
+        detections->newSources = sources;
+        return true;
+    }
+
+    if (setWindow) {
+        if (!psphotSetMomentsWindow(recipe, readout->analysis, sources)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Failed to determine Moments Window!");
+            psFree(sources);
+            return false;
+        }
+    }
+
+    // if we have measured the window, we will be saving the modified version of these recipe values on readout->analysis
+    float SIGMA = psMetadataLookupF32 (&status, readout->analysis, "MOMENTS_GAUSS_SIGMA");
+    if (!status) {
+        SIGMA = psMetadataLookupF32 (&status, recipe, "MOMENTS_GAUSS_SIGMA");
+    }
+    float RADIUS = psMetadataLookupF32 (&status, readout->analysis, "PSF_MOMENTS_RADIUS");
+    if (!status) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // threaded measurement of the source magnitudes
+    int Nfail = 0;
+    int Nmoments = 0;
+    int Nfaint = 0;
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_SOURCE_STATS");
+
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+
+            PS_ARRAY_ADD_SCALAR(job->args, INNER,   PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, MIN_SN,  PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, RADIUS,  PS_TYPE_F32);
+            PS_ARRAY_ADD_SCALAR(job->args, SIGMA,   PS_TYPE_F32);
+
+            PS_ARRAY_ADD_SCALAR(job->args, maskVal, PS_TYPE_IMAGE_MASK);
+            PS_ARRAY_ADD_SCALAR(job->args, markVal, PS_TYPE_IMAGE_MASK);
+
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nmoments
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfail
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfaint
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to launch thread job PSPHOT_SOURCE_STATS");
+                psFree (job);
+                psFree(sources);
+                return false;
+            }
+            psFree(job);
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false)) {
+            psError(PS_ERR_UNKNOWN, false, "Failure in thread job PSPHOT_SOURCE_STATS");
+            psFree(sources);
+            return false;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            } else {
+                psScalar *scalar = NULL;
+                scalar = job->args->data[7];
+                Nmoments += scalar->data.S32;
+                scalar = job->args->data[8];
+                Nfail += scalar->data.S32;
+                scalar = job->args->data[9];
+                Nfaint += scalar->data.S32;
+            }
+            psFree(job);
+        }
+    }
+
+    psFree (cellGroups);
+
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld sources, %d moments, %d faint, %d failed: %f sec\n", sources->n, Nmoments, Nfaint, Nfail, psTimerMark ("psphot.stats"));
+
+    psphotVisualShowMoments (sources);
+
+    // save the new sources on the detection structure:
+    detections->newSources = sources;
+
+
+    if (detections->allSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->allSources);
+    }
+    if (detections->newSources) {
+        psphotMaskCosmicRayFootprintCheck(detections->newSources);
+    }
+
+    return true;
+}
+
+// this function is currently only called by psphotCheckExtSources
+bool psphotSourceStatsUpdate (psArray *sources, pmConfig *config, pmReadout *readout) {
+
+    bool status = false;
+
+    psTimerStart ("psphot.stats");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // determine the number of allowed threads
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
+    if (!status) {
+        nThreads = 0;
+    }
+
+    // determine properties (sky, moments) of initial sources
+    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    if (!status) return false;
+
+    OUTER = PS_MAX(OUTER, 20.0); // XXX Guarantee that we can encompass the max moments radius
+
+    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
+    if (!status) return false;
+
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        if (!source->peak) continue; // XXX how can we have a peak-less source?
+
+        // allocate space for moments
+        if (!source->moments) {
+            source->moments = pmMomentsAlloc();
+        }
+
+        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
+        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+        source->peak->assigned = true;
+    }
+
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        psLogMsg ("psphot", PS_LOG_INFO, "%ld sources : %f sec\n", sources->n, psTimerMark ("psphot.stats"));
+        psLogMsg ("psphot", PS_LOG_INFO, "break point PEAKS, skipping MOMENTS\n");
+        psphotVisualShowMoments (sources);
+        return sources;
+    }
+
+    // XXX how else could we get the window size in?
+    if (!psphotSetMomentsWindow(recipe, readout->analysis, sources)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Failed to determine Moments Window!");
+        return NULL;
+    }
+
+    // threaded measurement of the source magnitudes
+    int Nfail = 0;
+    int Nmoments = 0;
+    int Nfaint = 0;
+
+    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
+    int Cx = 1, Cy = 1;
+    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
+
+    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
+
+    for (int i = 0; i < cellGroups->n; i++) {
+
+        psArray *cells = cellGroups->data[i];
+
+        for (int j = 0; j < cells->n; j++) {
+
+            // allocate a job -- if threads are not defined, this just runs the job
+            psThreadJob *job = psThreadJobAlloc ("PSPHOT_SOURCE_STATS");
+
+            // XXX: this must match the above
+            psArrayAdd(job->args, 1, cells->data[j]); // sources
+            psArrayAdd(job->args, 1, recipe);
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nmoments
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfail
+            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nfaint
+
+            if (!psThreadJobAddPending(job)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to launch thread job PSPHOT_SOURCE_STATS");
+                psFree (job);
+                return NULL;
+            }
+            psFree(job);
+        }
+
+        // wait for the threads to finish and manage results
+        if (!psThreadPoolWait (false)) {
+            psError(PS_ERR_UNKNOWN, false, "Failure in thread job PSPHOT_SOURCE_STATS");
+            return NULL;
+        }
+
+        // we have only supplied one type of job, so we can assume the types here
+        psThreadJob *job = NULL;
+        while ((job = psThreadJobGetDone()) != NULL) {
+            if (job->args->n < 1) {
+                fprintf (stderr, "error with job\n");
+            } else {
+                psScalar *scalar = NULL;
+                scalar = job->args->data[2];
+                Nmoments += scalar->data.S32;
+                scalar = job->args->data[3];
+                Nfail += scalar->data.S32;
+                scalar = job->args->data[4];
+                Nfaint += scalar->data.S32;
+            }
+            psFree(job);
+        }
+    }
+
+    psFree (cellGroups);
+
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld sources, %d moments, %d faint, %d failed: %f sec\n", sources->n, Nmoments, Nfaint, Nfail, psTimerMark ("psphot.stats"));
+
+    psphotVisualShowMoments (sources);
+
+    return (sources);
+}
+
+bool psphotSourceStats_Threaded (psThreadJob *job) {
+
+    bool status = false;
+    float BIG_RADIUS;
+    psScalar *scalar = NULL;
+
+    psArray *sources                = job->args->data[0];
+
+    float INNER                     = PS_SCALAR_VALUE(job->args->data[1],F32);
+    float MIN_SN                    = PS_SCALAR_VALUE(job->args->data[2],F32);
+    float RADIUS                    = PS_SCALAR_VALUE(job->args->data[3],F32);
+    float SIGMA                     = PS_SCALAR_VALUE(job->args->data[4],F32);
+
+    psImageMaskType maskVal         = PS_SCALAR_VALUE(job->args->data[5],PS_TYPE_IMAGE_MASK_DATA);
+    psImageMaskType markVal         = PS_SCALAR_VALUE(job->args->data[6],PS_TYPE_IMAGE_MASK_DATA);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // threaded measurement of the sources moments
+    int Nfail = 0;
+    int Nmoments = 0;
+    int Nfaint = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip faint sources for moments measurement
+        if (source->peak->SN < MIN_SN) {
+            source->mode |= PM_SOURCE_MODE_BELOW_MOMENTS_SN;
+            Nfaint++;
+            continue;
+        }
+
+        // measure a local sky value
+        // the local sky is now ignored; kept here for reference only
+        status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
+        if (!status) {
+            source->mode |= PM_SOURCE_MODE_SKY_FAILURE;
+            psErrorClear(); // XXX re-consider the errors raised here
+            Nfail ++;
+            continue;
+        }
+
+        // measure the local sky variance (needed if noise is not sqrt(signal))
+        // XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+        status = pmSourceLocalSkyVariance (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
+        if (!status) {
+            source->mode |= PM_SOURCE_MODE_SKYVAR_FAILURE;
+            Nfail ++;
+            psErrorClear();
+            continue;
+        }
+
+        // measure basic source moments (no S/N clipping on input pixels)
+        status = pmSourceMoments (source, RADIUS, SIGMA, 0.0, maskVal);
+        if (status) {
+            Nmoments ++;
+            continue;
+        }
+
+        // if no valid pixels, or massive swing, likely saturated source,
+        // try a much larger box
+        BIG_RADIUS = PS_MIN (INNER, 3*RADIUS);
+        psTrace ("psphot", 4, "retrying moments for %d, %d\n", source->peak->x, source->peak->y);
+        status = pmSourceMoments (source, BIG_RADIUS, 3.0*SIGMA, 0.0, maskVal);
+        if (status) {
+            source->mode |= PM_SOURCE_MODE_BIG_RADIUS;
+            Nmoments ++;
+            continue;
+        }
+
+        source->mode |= PM_SOURCE_MODE_MOMENTS_FAILURE;
+        Nfail ++;
+        psErrorClear();
+        continue;
+    }
+
+    // change the value of a scalar on the array (wrap this and put it in psArray.h)
+    scalar = job->args->data[7];
+    scalar->data.S32 = Nmoments;
+
+    scalar = job->args->data[8];
+    scalar->data.S32 = Nfail;
+
+    scalar = job->args->data[9];
+    scalar->data.S32 = Nfaint;
+
+    return true;
+}
+
+// this function attempts to iteratively determine the best value for sigma of the moments weighting Gaussian
+// this function modifies the recipe values MOMENTS_SX_MAX, MOMENTS_SY_MAX, and PSF_CLUMP_GRID_SCALE, used by pmSourcePSFClump
+bool psphotSetMomentsWindow (psMetadata *recipe, psMetadata *analysis, psArray *sources) {
+
+    bool status;
+
+    float MIN_SN = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN"); psAssert (status, "missing MOMENTS_SN_MIN");
+    float PSF_SN_LIM = psMetadataLookupF32(&status, recipe, "PSF_SN_LIM"); psAssert (status, "missing PSF_SN_LIM");
+    psF32 MOMENTS_AR_MAX = psMetadataLookupF32(&status, recipe, "MOMENTS_AR_MAX"); psAssert (status, "missing MOMENTS_AR_MAX");
+
+    // XXX move this to a config file?
+    # define NSIGMA 8
+    float sigma[NSIGMA] = {1.0, 2.0, 3.0, 4.5, 6.0, 9.0, 12.0, 18.0};
+    float Sout[NSIGMA];
+
+    // this sorts by peak->SN
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // loop over radii:
+    for (int i = 0; i < NSIGMA; i++) {
+
+        // XXX move max source number to config
+        for (int j = 0; (j < sources->n) && (j < 400); j++) {
+
+            pmSource *source = sources->data[j];
+            psAssert (source->moments, "force moments to exist");
+            source->moments->nPixels = 0;
+
+            // skip faint sources for moments measurement
+            if (source->peak->SN < MIN_SN) {
+                continue;
+            }
+
+            // measure basic source moments (no S/N clipping on input pixels)
+            status = pmSourceMoments (source, 4*sigma[i], sigma[i], 0.0, 0xffff);
+        }
+
+        // choose a grid scale that is a fixed fraction of the psf sigma^2
+        float PSF_CLUMP_GRID_SCALE = 0.1*PS_SQR(sigma[i]);
+        float MOMENTS_SX_MAX = 2.0*PS_SQR(sigma[i]);
+        float MOMENTS_SY_MAX = 2.0*PS_SQR(sigma[i]);
+
+        // determine the PSF parameters from the source moment values
+        pmPSFClump psfClump = pmSourcePSFClump (NULL, NULL, sources, PSF_SN_LIM, PSF_CLUMP_GRID_SCALE, MOMENTS_SX_MAX, MOMENTS_SY_MAX, MOMENTS_AR_MAX);
+        psLogMsg ("psphot", 3, "radius %.1f, nStars: %d, nSigma: %5.2f, X,  Y: %f, %f (%f, %f)\n", sigma[i], psfClump.nStars, psfClump.nSigma, psfClump.X, psfClump.Y, sqrt(psfClump.X) / sigma[i], sqrt(psfClump.Y) / sigma[i]);
+
+        psMetadataAddS32 (analysis, PS_LIST_TAIL, "PSF.CLUMP.NREGIONS",  PS_META_REPLACE, "psf clump regions", 1);
+        psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, "PSF.CLUMP.REGION.000");
+        if (!regionMD) {
+            regionMD = psMetadataAlloc();
+            psMetadataAddMetadata (analysis, PS_LIST_TAIL, "PSF.CLUMP.REGION.000", PS_META_REPLACE, "psf clump region", regionMD);
+            psFree (regionMD);
+        }
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
+        psMetadataAddF32 (regionMD, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
+
+        psphotVisualPlotMoments (recipe, analysis, sources);
+
+        Sout[i] = sqrt(0.5*(psfClump.X + psfClump.Y)) / sigma[i];
+    }
+
+    // we are looking for sigma for which Sout = 0.65 (or some other value)
+    float Sigma = NAN;
+    float minS = Sout[0];
+    float maxS = Sout[0];
+    for (int i = 0; i < NSIGMA; i++) {
+        minS = PS_MIN(Sout[i], minS);
+        maxS = PS_MAX(Sout[i], maxS);
+    }
+    if (minS > 0.65) Sigma = sigma[NSIGMA-1];
+    if (maxS < 0.65) Sigma = sigma[0];
+
+    for (int i = 0; i < NSIGMA - 1 && isnan(Sigma); i++) {
+        if (!isfinite(Sout[i]) || !isfinite(Sout[i+1])) continue;
+        if ((Sout[i] > 0.65) && (Sout[i+1] > 0.65)) continue;
+        if ((Sout[i] < 0.65) && (Sout[i+1] < 0.65)) continue;
+        Sigma = sigma[i] + (0.65 - Sout[i])*(sigma[i+1] - sigma[i])/(Sout[i+1] - Sout[i]);
+    }
+    psAssert (isfinite(Sigma), "did we miss a case?");
+
+    // choose a grid scale that is a fixed fraction of the psf sigma^2
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "PSF_CLUMP_GRID_SCALE", PS_META_REPLACE, "clump grid", 0.1*PS_SQR(Sigma));
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "MOMENTS_SX_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(Sigma));
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "MOMENTS_SY_MAX", PS_META_REPLACE, "moments limit", 2.0*PS_SQR(Sigma));
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "MOMENTS_GAUSS_SIGMA", PS_META_REPLACE, "moments limit", Sigma);
+    psMetadataAddF32(analysis, PS_LIST_TAIL, "PSF_MOMENTS_RADIUS", PS_META_REPLACE, "moments limit", 4.0*Sigma);
+
+    psLogMsg ("psphot", 3, "using window function with sigma = %f\n", Sigma);
+    return true;
+}
+
+// if we use the footprints, the output peaks list contains both old and new peaks,
+// otherwise it only contains the new peaks.
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotStack.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotStack.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotStack.c	(revision 27622)
@@ -0,0 +1,35 @@
+# include "psphotStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();                  // register psModule's error codes/messages
+    psphotInit();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotStackArguments (argc, argv);
+    assert(config);
+
+    psphotVersionPrint();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotStackParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotStackImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackArguments.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackArguments.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackArguments.c	(revision 27622)
@@ -0,0 +1,159 @@
+# include "psphotStandAlone.h"
+
+static void usage(pmConfig *config, int exitCode);
+static void writeHelpInfo(pmConfig* config, FILE* ofile);
+
+pmConfig *psphotStackArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    // print help info
+    if (psArgumentGet(argc, argv, "-help")) writeHelpInfo(argv[0], config, stdout);
+    if (psArgumentGet(argc, argv, "-h")) writeHelpInfo(argv[0], config, stdout);
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+      psErrorStackPrint(stderr, "Can't read site configuration");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // -version and -dumpconfig arguments
+    PSARGUMENTS_INSTANTIATE_GENERICS( psphot, config, argc, argv );
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // Number of threads is handled
+    PSARGUMENTS_INSTANTIATE_THREADSARG( psphot, config, argc, argv )
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        if (argc <= N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // visual : interactive display mode
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // break : used from recipe throughout psphotReadout to stop processing early
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+	if (argc <= N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // find the input data file (an mdc file)
+    if ((N = psArgumentGet(argc, argv, "-input"))) {
+	if (argc <= N+1) {
+	  psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+	  exit(PS_EXIT_CONFIG_ERROR);
+	}
+        psArgumentRemove(N, &argc, argv);
+
+        unsigned int numBad = 0;                     // Number of bad lines
+        psMetadata *inputs = psMetadataConfigRead(NULL, &numBad, argv[N], false); // Input file info
+        if (!inputs || numBad > 0) {
+	    psErrorStackPrint(stderr, "Unable to cleanly read MDC file with inputs.");
+	    exit(PS_EXIT_CONFIG_ERROR);
+        }
+        psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "INPUTS", 0, "Metadata with input details", inputs);
+        psFree(inputs);
+
+        psArgumentRemove(N, &argc, argv);
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+	usage(config, PS_EXIT_CONFIG_ERROR);
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotArguments...\n");
+    return (config);
+}
+
+static void writeHelpInfo(pmConfig* config, FILE* ofile)
+{
+  fprintf(ofile,
+	  "Usage: psphotStack -input (input.mdc) outroot\n"
+	  "\n"
+	  "where:\n"
+	  "  FileNameList is a text file containing filenames, one per line\n"
+	  "  MaskFileNameList is a text file of mask filenames, one per line\n"
+	  "  VarFileNameList is a text file of variance filenames, one per line\n"
+	  "  OutFileBaseName is the 'root name' for output files\n"
+	  "\n"
+	  "additional options:\n"
+	  "  -chip nn[,nn,...]\n"
+	  "     select detector chips to process; default is all.\n"
+	  "     Indices correspond to zero-based offset in the FPA metadata table.\n"
+	  "  -photcode PhotoCodeName\n"
+	  "     specify photocode\n"
+	  "  -region RegionString\n"
+	  "     specify analysis region.  String is of form '[x0:x1,y0:y1]'\n"
+	  "     To use this option you must define a default in psphot.config\n"
+	  "  -visual\n"
+	  "     turns on interactive display mode\n"
+	  "  -dumpconfig CfgFileName\n"
+          "     causes config info to be dumped to the named file.\n"
+	  "  -break NOTHING|BACKMDL|PEAKS|MOMENTS|PSFMODEL|ENSEMBLE|PASS1\n"
+	  "     choose a point at which to exit processing early\n"
+	  "  -nthreads n\n"
+	  "     set number of parallel threads of execution\n"
+	  "  -F OldFileRule ReplacementFileRule\n"
+	  "     change file naming rule; e.g. '-F PSPHOT.OUTPUT PSPHOT.OUT.CMF.MEF'\n"
+	  "  -D name stringval\n"
+	  "     set a string-valued config parameter\n"
+	  "  -Di name intval\n"
+	  "     set an integer-valued config parameter\n"
+	  "  -Df name fval\n"
+	  "     set a float-valued config parameter\n"
+	  "  -Db name boolval\n"
+	  "     set a boolean-valued config parameter\n"
+	  "  -v, -vv, -vvv\n"
+	  "     set increasing levels of verbosity\n"
+	  "  -logfmt FormatString\n"
+	  "     set format string used for log messages\n"
+	  "  -trace Fac Lvl\n"
+	  "     set tracing for facility Fac to integer Lvl, e.g. '-trace err 10'\n"
+	  "  -trace-levels\n"
+	  "     print current trace levels\n");
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(PS_EXIT_SUCCESS);
+}
+
+static void usage(pmConfig *config,      // Configuration
+		  int exitCode
+		  ) 
+{
+    fprintf(stderr, 
+	    "Usage: psphotStack -input input.mdc outroot\n"
+	    "Try 'psphotStack -help' for more options and explanation\n");
+
+    if (exitCode != PS_EXIT_SUCCESS) psErrorStackPrint(stderr, "Error reading arguments\n");
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(exitCode);
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackChisqImage.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackChisqImage.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackChisqImage.c	(revision 27622)
@@ -0,0 +1,84 @@
+# include "psphotInternal.h"
+
+// Chi = sqrt(\sum_i im_i^2/var_i)
+
+static int npass = 0;
+
+// XXX supply filename or keep PSPHOT.INPUT fixed?
+bool psphotStackChisqImage (pmConfig *config, const pmFPAview *view)
+{
+    bool status = false;
+
+    psTimerStart ("psphot.chisq.image");
+
+    // create a holder for the image
+    pmReadout *chiImage = pmReadoutAlloc();
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+        if (!psphotStackChisqImageAddReadout(config, view, chiImage, "PSPHOT.INPUT", i)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to model background for PSPHOT.INPUT entry %d", i);
+            return false;
+        }
+    }
+
+    // need to save the resulting image somewhere (PSPHOT.INPUT?)
+
+    psLogMsg ("psphot", PS_LOG_INFO, "built chisq image: %f sec\n", psTimerMark ("psphot.chisq.image"));
+
+    return true;
+}
+
+bool psphotStackChisqImageAddReadout(const pmConfig *config, // Configuration
+				     pmFPAview *view,
+				     pmReadout *chiReadout,
+				     char *filename, 
+				     int index) 
+{
+    bool status = true;
+
+    // find the currently selected readout
+    pmFPAfile *input = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+    psAssert (input, "missing file?");
+
+    pmReadout *inReadout = pmFPAviewThisReadout(view, input->fpa);
+    psAssert (inReadout, "missing readout?");
+
+    psImage *inImage     = inReadout->image;
+    psImage *inVariance  = inReadout->variance;
+    psImage *inMask      = inReadout->mask;
+
+    psImage *chiImage    = chiReadout->image;
+    psImage *chiVariance = chiReadout->variance;
+    psImage *chiMask     = chiReadout->mask;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // generate the chisq image:
+    for (int iy = 0; iy < inImage->numRows; iy++) {
+        for (int ix = 0; ix < inImage->numCols; ix++) {
+	    if (inMask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] & maskVal) continue;
+	    chiImage->data.F32[iy][ix] += PS_SQR(inImage->data.F32[iy][ix]) / inVariance->data.F32[iy][ix];
+	    chiVariance->data.F32[iy][ix] = 1.0; // ?? what is the right value?  just init to this?
+	    chiMask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] = 0x00; // we have valid data so unmask this pixel
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "chiImage.%02d.fits", npass);
+        psphotSaveImage (NULL, chiImage, name);
+    }
+    npass ++;
+
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackImageLoop.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackImageLoop.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackImageLoop.c	(revision 27622)
@@ -0,0 +1,35 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // XXX for now, just load the full set of images up front
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // XXX for now, we assume there is only a single chip in the PHU:
+    psphotStackReadout (config, view);
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+/* 
+   the easiest way to implement this is to assume we can pre-load the full set of images up front.
+   with 5 filters and 6000^2 (image, mask, var = 10 byte per pixel), we need 1.8GB, which is not too bad.
+ */
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackParseCamera.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackParseCamera.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackParseCamera.c	(revision 27622)
@@ -0,0 +1,102 @@
+# include "psphotStandAlone.h"
+
+static pmFPAfile *defineFile(pmConfig *config, pmFPAfile *bind, const char *name, const char *filename, pmFPAfileType type);
+
+// define the needed / desired I/O files
+bool psphotStackParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the input images are defined as a set of metadatas in the INPUTS metadata folder
+    psMetadata *inputs = psMetadataLookupMetadata(&status, config->arguments, "INPUTS"); // The inputs info
+    if (!inputs) {
+	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find inputs.");
+	return false;
+    }
+
+    psMetadata *item == NULL;
+    int nInputs = 0;
+    while ((item = psMetadataGetIndex(inputs, nInputs)) != NULL) {
+	if (item->type != PS_DATA_METADATA) {
+	    psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Component %s of the input metadata is not of type METADATA", item->name);
+	    return false;
+	}
+
+	psMetadata *input = item->data.md; // The input metadata of interest
+
+	// look for 'IMAGE', 'MASK', 'VARIANCE' in folder (only 'IMAGE' is required)
+
+	psString image = psMetadataLookupStr(NULL, input, "IMAGE"); // Name of image
+	if (!image || strlen(image) == 0) {
+	    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Component %s lacks IMAGE of type STR", item->name);
+	    return false;
+	}
+	pmFPAfile *imageFile = defineFile(config, NULL, "PSPHOT.INPUT", image, PM_FPA_FILE_IMAGE); // File for image
+	if (!imageFile) {
+	    psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", i, image);
+	    return false;
+	}
+
+	psString mask = psMetadataLookupStr(&status, input, "MASK"); // Name of mask
+	if (mask && strlen(mask) > 0) {
+	    if (!defineFile(config, imageFile, "PSPHOT.INPUT.MASK", mask, PM_FPA_FILE_MASK)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", i, mask);
+		return false;
+	    }
+	}
+
+	psString variance = psMetadataLookupStr(&mdok, input, "VARIANCE"); // Name of variance map
+	if (variance && strlen(variance) > 0) {
+	    if (!defineFile(config, imageFile, "PSPHOT.INPUT.VARIANCE", variance, PM_FPA_FILE_VARIANCE)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to define file from variance %d (%s)", i, variance);
+		return false;
+	    }
+	}
+	nInputs ++;
+    }
+    psMetadataRemoveKey(config->arguments, "FILENAMES");
+    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.INPUT.NUM", PS_META_REPLACE, "number of inputs", nInputs);
+
+    // define the additional input/output files associated with psphot
+    // XXX figure out which files are needed by psphotStack
+    if (false && !psphotDefineFiles (config, input)) {
+        psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files");
+        return false;
+    }
+
+    psTrace("psphot", 1, "Done with psphotStackParseCamera...\n");
+
+    psErrorClear();                     // some metadata lookup may have failed
+    return true;
+}
+
+// Define a file
+static pmFPAfile *defineFile(pmConfig *config, // Configuration
+                             pmFPAfile *bind, // File to which to bind
+                             const char *name, // Name of file rule
+                             const char *filename, // Name of file
+                             pmFPAfileType type // Type of file
+                             )
+{
+
+    psArray *dummy = psArrayAlloc(1);   // Dummy array of filenames for this FPA
+    dummy->data[0] = psStringCopy(filename);
+    psMetadataAddArray(config->arguments, PS_LIST_TAIL, "FILENAMES", PS_META_REPLACE,
+                       "Filenames for file rule definition", dummy);
+    psFree(dummy);
+
+    bool found = false;             // Found the file?
+    pmFPAfile *file = bind ? pmFPAfileBindFromArgs(&found, bind, config, name, "FILENAMES") :
+        pmFPAfileDefineFromArgs(&found, config, name, "FILENAMES");
+    if (!file || !found) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to define file %s from %s", name, filename);
+        return NULL;
+    }
+    if (file->type != type) {
+        psError(PS_ERR_IO, true, "%s is not of type %s", name, pmFPAfileStringFromType(type));
+        return NULL;
+    }
+
+    return file;
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackReadout.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackReadout.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotStackReadout.c	(revision 27622)
@@ -0,0 +1,207 @@
+# include "psphotInternal.h"
+
+bool psphotStackReadout (pmConfig *config, const pmFPAview *view) {
+
+    psTimerStart ("psphotReadout");
+
+    pmModelClassSetLimits(PM_MODEL_LIMITS_LAX); // allow models to have ugly fits (eg, central cusp)
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set the photcode for this image (XXX currently goes into recipe, should it go into analysis?)
+    if (!psphotAddPhotcode (config, view)) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotSetMaskAndVariance (config, view, recipe)) {
+        return psphotReadoutCleanup (config, NULL, recipe, NULL, NULL, NULL);
+    }
+    if (!strcasecmp (breakPt, "NOTHING")) {
+	return psphotReadoutCleanup(config, NULL, recipe, NULL, NULL, NULL);
+    }
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    PS_ASSERT_PTR_NON_NULL (breakPt, false);
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view)) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!psphotSubtractBackground (config, view)) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+
+    // find the detections (by peak and/or footprint) in the image.
+    pmDetections *detections = psphotFindDetections (NULL, readout, recipe);
+    if (!detections) {
+        psLogMsg ("psphot", 3, "unable to find detections in this image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, NULL);
+    }
+
+    // construct sources and measure basic stats
+    psArray *sources = psphotSourceStats (config, readout, detections, true);
+    if (!sources) return false;
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // find blended neighbors of very saturated stars
+    // XXX merge this with Basic Deblend?
+    psphotDeblendSatstars (readout, sources, recipe);
+
+    // mark blended peaks PS_SOURCE_BLEND
+    if (!psphotBasicDeblend (sources, recipe)) {
+        psLogMsg ("psphot", 3, "failed on deblend analysis");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // classify sources based on moments, brightness
+    if (!psphotRoughClass (readout, sources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+    if (!strcasecmp (breakPt, "MOMENTS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    if (!havePSF && !psphotImageQuality (recipe, sources)) {
+        psLogMsg("psphot", 3, "failed to measure image quality");
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // if we were not supplied a PSF, choose one here
+    if (psf == NULL) {
+        // use bright stellar objects to measure PSF
+        // XXX if we do not have enough stars to generate the PSF, build one
+        // from the SEEING guess and model class
+        psf = psphotChoosePSF (readout, sources, recipe);
+        if (psf == NULL) {
+            psLogMsg ("psphot", 3, "failure to construct a psf model");
+            return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        }
+        havePSF = true;
+    }
+    if (!strcasecmp (breakPt, "PSFMODEL")) {
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+    psphotVisualShowPSFModel (readout, psf);
+
+    // include externally-supplied sources
+    psphotLoadExtSources (config, view, sources);
+
+    // construct an initial model for each object, set the radius to fitRadius, set circular fit mask
+    psphotGuessModels (config, readout, sources, psf);
+
+    // linear PSF fit to source peaks, subtract the models from the image (in PSF mask)
+    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
+
+    // We have to place these visualizations here because the models are not realized until
+    // psphotGuessModels or fitted until psphotFitSourcesLinear.
+    psphotVisualShowPSFStars (recipe, psf, sources);
+
+    // identify CRs and extended sources
+    psphotSourceSize (config, readout, sources, recipe, psf, 0);
+    if (!strcasecmp (breakPt, "ENSEMBLE")) {
+        goto finish;
+    }
+    psphotVisualShowSatStars (recipe, psf, sources);
+
+    // non-linear PSF and EXT fit to brighter sources
+    // replace model flux, adjust mask as needed, fit, subtract the models (full stamp)
+    psphotBlendFit (config, readout, sources, psf);
+
+    // replace all sources
+    psphotReplaceAllSources (sources, recipe);
+
+    // linear fit to include all sources (subtract again)
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+
+    // if we only do one pass, skip to extended source analysis
+    if (!strcasecmp (breakPt, "PASS1")) {
+        goto pass1finish;
+    }
+    // NOTE: possibly re-measure background model here with objects subtracted
+
+    // add noise for subtracted objects
+    psphotAddNoise (readout, sources, recipe);
+
+    // find fainter sources (pass 2)
+    detections = psphotFindDetections (detections, readout, recipe);
+
+    // remove noise for subtracted objects (ie, return to normal noise level)
+    psphotSubNoise (readout, sources, recipe);
+
+    // define new sources based on only the new peaks
+    psArray *newSources = psphotSourceStats (config, readout, detections, false);
+
+    // set source type
+    if (!psphotRoughClass (readout, newSources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // create full input models, set the radius to fitRadius, set circular fit mask
+    psphotGuessModels (config, readout, newSources, psf);
+
+    // replace all sources so fit below applies to all at once
+    psphotReplaceAllSources (sources, recipe);
+
+    // merge the newly selected sources into the existing list
+    psphotMergeSources (sources, newSources);
+    psFree (newSources);
+
+    // linear fit to all sources
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+
+pass1finish:
+
+    // measure source size for the remaining sources
+    psphotSourceSize (config, readout, sources, recipe, psf, 0);
+
+    psphotExtendedSourceAnalysis (readout, sources, recipe);
+
+    psphotExtendedSourceFits (readout, sources, recipe);
+
+finish:
+
+    // plot positive sources
+    // psphotSourcePlots (readout, sources, recipe);
+
+    // measure aperture photometry corrections
+    if (!psphotApResid (config, readout, sources, psf)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // calculate source magnitudes
+    psphotMagnitudes(config, readout, view, sources, psf);
+
+    if (!psphotEfficiency(config, readout, view, psf, recipe, sources)) {
+        psErrorStackPrint(stderr, "Unable to determine detection efficiencies from fake sources");
+        psErrorClear();
+    }
+
+    // replace failed sources?
+    // psphotReplaceUnfitSources (sources);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (sources);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotStandAlone.h
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotStandAlone.h	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotStandAlone.h	(revision 27622)
@@ -0,0 +1,21 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#ifndef PSPHOT_STAND_ALONE_H
+#define PSPHOT_STAND_ALONE_H
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+// Top level functions
+pmConfig       *psphotArguments (int argc, char **argv);
+bool            psphotParseCamera (pmConfig *config);
+bool            psphotImageLoop (pmConfig *config);
+bool            psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile);
+void            psphotCleanup (pmConfig *config);
+psExit          psphotGetExitStatus (void);
+
+#endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSubtractBackground.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSubtractBackground.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSubtractBackground.c	(revision 27622)
@@ -0,0 +1,139 @@
+# include "psphotInternal.h"
+static int npass = 0;
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotSubtractBackgroundReadout (pmConfig *config, const pmFPAview *view, const char *filename, int index, psMetadata *recipe)
+{
+    bool status = true;
+    pmReadout *background = NULL;
+    pmReadout *backSub = NULL;
+
+    psTimerStart ("psphot.background");
+
+    // find the currently selected readout
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, filename, index); // File of interest
+
+    pmFPA *inFPA = file->fpa;
+    pmReadout *readout = pmFPAviewThisReadout (view, inFPA);
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // find the currently selected readout (XXX note that the model is saved on PSPHOT.BACKMDL regardless of 'filename'
+    pmFPAfile *modelFile = pmFPAfileSelectSingle(config->files, "PSPHOT.BACKMDL", index); // File of interest
+    assert (modelFile);
+    pmReadout *model = pmFPAviewThisReadout (view, modelFile->fpa);
+    assert (model);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    psImageBinning *binning = psMetadataLookupPtr(&status, model->analysis, "PSPHOT.BACKGROUND.BINNING");
+    assert (binning);
+
+    // select background pixels, from output background file, or create
+    // XXX for now, we will only allow a single background image to be generated
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (file) {
+        // we are using PSPHOT.BACKGND as an I/O file: select readout or create
+        if (file->mode == PM_FPA_MODE_INTERNAL) {
+            background = file->readout;
+        } else {
+            background = pmFPAviewThisReadout (view, file->fpa);
+        }
+        if (background == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            background = pmFPAviewThisReadout (view, file->fpa);
+            if ((image->numCols != background->image->numCols) || (image->numRows != background->image->numRows)) {
+                psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for background dimensions");
+                return false;
+            }
+        }
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", image->numCols, image->numRows, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    if (!psImageUnbin (background->image, model->image, binning)) {
+        psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for unbinning");
+        return false;
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build resampled image: %f sec\n", psTimerMark ("psphot.background"));
+
+    // back-sub image pixels, from output background file (don't create if not requested)
+    // XXX for now, we will only allow a single background-subtracted image to be generated
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
+    if (file) {
+        // we are using PSPHOT.BACKSUB as an I/O file: select readout or create
+        backSub = pmFPAviewThisReadout (view, file->fpa);
+        if (backSub == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            backSub = pmFPAviewThisReadout (view, file->fpa);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "image.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+        sprintf (name, "back.%02d.fits", npass);
+        psphotSaveImage (NULL, background->image, name);
+        sprintf (name, "mask.%02d.fits", npass);
+        psphotSaveImage (NULL, mask, name);
+        sprintf (name, "backmdl.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // subtract the background model (save in backSub, if requested)
+    // XXX if needed, multithread this (fairly trivial)
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+            image->data.F32[j][i] -= backData[j][i];
+            if (backSub) {
+                backSub->image->data.F32[j][i] = image->data.F32[j][i];
+            }
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backsub.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "subtracted background model: %f sec\n", psTimerMark ("psphot.background"));
+
+    // the pmReadout selected in this function are all view on entries in config->files
+
+    // display the backsub and backgnd images
+    // move this inthe the subtract background loop
+    psphotVisualShowBackground (config, view, readout);
+
+    npass ++;
+    return true;
+}
+
+bool psphotSubtractBackground (pmConfig *config, const pmFPAview *view)
+{
+    bool status = false;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    psAssert (recipe, "missing recipe?");
+
+    int num = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.INPUT.NUM");
+    psAssert (status, "programming error: must define PSPHOT.INPUT.NUM");
+
+    // loop over the available readouts
+    for (int i = 0; i < num; i++) {
+	if (!psphotSubtractBackgroundReadout (config, view, "PSPHOT.INPUT", i, recipe)) {
+            psError (PSPHOT_ERR_CONFIG, false, "failed to subtract background for PSPHOT.INPUT entry %d", i);
+	    return false;
+	}
+    }
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotSummaryPlots.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotSummaryPlots.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotSummaryPlots.c	(revision 27622)
@@ -0,0 +1,280 @@
+# include "psphotInternal.h"
+
+// the top portion of this file defines plotting functions which use kapa for plotting.
+// if kapa is not available, these functions are defined in the bottom portion as stubs
+// which perform NOP and return false (XXX should this be true??)
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+
+# include <kapa.h>
+
+// plot the sx, sy moments plane (faint and bright sources)
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.MOMENT.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping moments plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating moments plot");
+
+    // XXX get the 'showWindow' option from the recipes somewhere
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    KapaResize (kapa, 500, 500);
+    KapaInitGraph (&graphdata);
+
+    // examine sources to set data range
+    graphdata.xmin = -0.05;
+    graphdata.ymin = -0.05;
+    graphdata.xmax = +2.05;
+    graphdata.ymax = +2.05;
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "&ss&h_x| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "&ss&h_y| (pixels)", KAPA_LABEL_YM);
+	       
+    psVector *xBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *xFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // construct the vectors
+    int nB = 0;
+    int nF = 0;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+    
+	xFaint->data.F32[nF] = source->moments->Mxx;
+	yFaint->data.F32[nF] = source->moments->Myy;
+	nF++;
+    
+	// XXX make this a user-defined cutoff
+	if (source->moments->SN < 25) continue;
+
+	xBright->data.F32[nB] = source->moments->Mxx;
+	yBright->data.F32[nB] = source->moments->Myy;
+	nB++;
+    }
+    xFaint->n = nF;
+    yFaint->n = nF;
+
+    xBright->n = nB;
+    yBright->n = nB;
+  
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nF, &graphdata);
+    KapaPlotVector (kapa, nF, xFaint->data.F32, "x");
+    KapaPlotVector (kapa, nF, yFaint->data.F32, "y");
+  
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nB, &graphdata);
+    KapaPlotVector (kapa, nB, xBright->data.F32, "x");
+    KapaPlotVector (kapa, nB, yBright->data.F32, "y");
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xBright);
+    psFree (yBright);
+    psFree (xFaint);
+    psFree (yFaint);
+
+    return true;
+}
+
+// plot the sx, sy, sxy as vector field, 
+// plot the PSF measured sx, sy, sxy as vector field
+// pull the sources from the config / file?
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.PSFMODEL.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping psf model plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating psf model plot");
+
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    // XXX make the aspect-ratio match the image
+    KapaResize (kapa, 800, 800);
+    KapaInitGraph (&graphdata);
+
+    psVector *xMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+
+    // construct the plot vectors
+    int nMNT = 0;
+    int nPSF = 0;
+    int nMIN = 0;
+    float dx = 0;
+    float dy = 0;
+    float scale = 10;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+	if (source->moments->SN < 25) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+    
+	pmModel *model = source->modelPSF;
+        if (model == NULL) continue;
+
+	psF32 *PAR = model->params->data.F32;
+
+	psEllipseMoments moments;
+	moments.x2 = source->moments->Mxx;
+	moments.xy = source->moments->Mxy;
+	moments.y2 = source->moments->Myy;
+
+	psEllipseShape shape;
+	shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+	shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+	shape.sxy = PAR[PM_PAR_SXY];
+
+	// force the axis ratio to be < 20.0
+	psEllipseAxes axes_mnt = psEllipseMomentsToAxes (moments, 20.0);
+	psEllipseAxes axes_psf = psEllipseShapeToAxes (shape, 20.0);
+
+	// moments major axis
+	dx = scale*axes_mnt.major*cos(axes_mnt.theta);
+	dy = scale*axes_mnt.major*sin(axes_mnt.theta);
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] - dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] - dy;
+	nMNT++;
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] + dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] + dy;
+	nMNT++;
+    
+	// psf major axis
+	dx = scale*axes_psf.major*cos(axes_psf.theta);
+	dy = scale*axes_psf.major*sin(axes_psf.theta);
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] - dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] - dy;
+	nPSF++;
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] + dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] + dy;
+	nPSF++;
+
+	// minor axis (to show size)
+	dy = +scale*axes_psf.minor*cos(axes_psf.theta);
+	dx = -scale*axes_psf.minor*sin(axes_psf.theta);
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] - dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] - dy;
+	nMIN++;
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] + dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] + dy;
+	nMIN++;
+
+	graphdata.xmin = PS_MIN(graphdata.xmin, PAR[PM_PAR_XPOS]);
+	graphdata.xmax = PS_MAX(graphdata.xmax, PAR[PM_PAR_XPOS]);
+	graphdata.ymin = PS_MIN(graphdata.ymin, PAR[PM_PAR_YPOS]);
+	graphdata.ymax = PS_MAX(graphdata.ymax, PAR[PM_PAR_YPOS]);
+    }
+    xMNT->n = yMNT->n = nMNT;
+    xPSF->n = yPSF->n = nPSF;
+    xMIN->n = yMIN->n = nMIN;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX set the plot range to match the image
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "x (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "y (pixels)", KAPA_LABEL_YM);
+    KapaSendLabel (kapa, "vector is major axis (scale by 20) : black are moments, blue are psf model, red is psf minor axis", KAPA_LABEL_XP);
+	       
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 100;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMNT, &graphdata);
+    KapaPlotVector (kapa, nMNT, xMNT->data.F32, "x");
+    KapaPlotVector (kapa, nMNT, yMNT->data.F32, "y");
+  
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nPSF, &graphdata);
+    KapaPlotVector (kapa, nPSF, xPSF->data.F32, "x");
+    KapaPlotVector (kapa, nPSF, yPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMIN, &graphdata);
+    KapaPlotVector (kapa, nMIN, xMIN->data.F32, "x");
+    KapaPlotVector (kapa, nMIN, yMIN->data.F32, "y");
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xMNT);
+    psFree (yMNT);
+    psFree (xPSF);
+    psFree (yPSF);
+    psFree (xMIN);
+    psFree (yMIN);
+
+    return true;
+}
+
+# else
+
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping moments plot");
+    return true;
+}
+
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping psf model plot");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotTest.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotTest.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotTest.c	(revision 27622)
@@ -0,0 +1,188 @@
+# include "psphotInternal.h"
+
+bool FillImage_Threaded (psThreadJob *job);
+
+bool SetThreads () {
+
+    psThreadTask *task = NULL;
+
+    task = psThreadTaskAlloc("FILL_IMAGE", 6);
+    task->function = &FillImage_Threaded;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    return true;
+}
+
+bool FillImage (psImage *image, int xs, int ys, int dx, int dy, int value) {
+
+    psRegion region = psRegionSet (xs, xs + dx, ys, ys + dy);
+    psImage *subset = psImageSubset (image, region);
+    psImageInit (subset, value);
+    psFree (subset);
+    return true;
+}
+
+bool FillImage_Threaded (psThreadJob *job) {
+    
+    psImage *image = job->args->data[0];
+    int xs = PS_SCALAR_VALUE(job->args->data[1],S32);
+    int ys = PS_SCALAR_VALUE(job->args->data[2],S32);
+    int dx = PS_SCALAR_VALUE(job->args->data[3],S32);
+    int dy = PS_SCALAR_VALUE(job->args->data[4],S32);
+    int value = PS_SCALAR_VALUE(job->args->data[5],S32);
+
+    // we want the threads to be likely to interact.  run lots of psImageSubsets
+    psRegion region = psRegionSet (xs, xs + dx, ys, ys + dy);
+    for (int i = 0; i < 100; i++) {
+	psImage *subset = psImageSubset (image, region);
+	psImageInit (subset, value + i);
+	psFree (subset);
+    }
+    return true;
+}
+
+int main (int argc, char **argv) {
+
+    if (argc != 3) {
+	fprintf (stderr, "USAGE: psphotTest (output.fits) (nThreads)\n");
+	exit (2);
+    }
+
+    (void) psTraceSetLevel ("psLib.sys.mutex", 3);
+
+    int nThreads = atoi (argv[2]);
+
+    // create the thread pool with number of desired threads, supplying our thread launcher function
+    psThreadPoolInit (nThreads);
+
+    SetThreads();
+
+    psImage *image = psImageAlloc (1000, 1000, PS_TYPE_S32);
+
+    for (int ix = 0; ix < 1000; ix += 100) {
+	for (int iy = 0; iy < 1000; iy += 100) {
+
+	    // allocate a job -- if threads are not defined, this just runs the job
+	    psThreadJob *job = psThreadJobAlloc ("FILL_IMAGE");
+
+	    psArrayAdd(job->args, 1, image);
+	    PS_ARRAY_ADD_SCALAR(job->args, ix, PS_TYPE_S32);
+	    PS_ARRAY_ADD_SCALAR(job->args, iy, PS_TYPE_S32);
+	    PS_ARRAY_ADD_SCALAR(job->args, 100, PS_TYPE_S32);
+	    PS_ARRAY_ADD_SCALAR(job->args, 100, PS_TYPE_S32);
+	    PS_ARRAY_ADD_SCALAR(job->args, ix + iy, PS_TYPE_S32);
+
+	    // FillImage (image, ix, iy, 100, 100, ix + iy);
+
+	    if (!psThreadJobAddPending(job)) {
+		fprintf (stderr, "failure to run FillImage(1)");
+		psFree (job);
+		exit (1);
+	    }
+	    psFree(job);
+	}
+    }
+
+
+    // wait for the threads to finish and manage results
+    if (!psThreadPoolWait (true)) {
+	fprintf (stderr, "failure to run FillImage (2)");
+	exit (1);
+    }
+
+    psFits *fits = psFitsOpen (argv[1], "w");
+    psFitsWriteImage (fits, NULL, image, 0, NULL);
+    psFitsClose (fits);
+
+    psThreadPoolFinalize ();
+    psFree(image);
+
+    fprintf (stderr, "found %d leaks\n", psMemCheckLeaks (0, NULL, stdout, false));
+    exit (0);
+}
+
+# if (0)
+
+    psRegion region = {0,0,0,0};        // a region representing the entire array
+    psphotTestArguments (&argc, argv);
+
+    psFits *file = psFitsOpen (argv[1], "r");
+    psMetadata *header = psFitsReadHeader (NULL, file);
+    psImage *image = psFitsReadImage (NULL, file, region, 0);
+    psFitsClose (file);
+
+    psImageJpegColormap (argv[5]);
+
+    // psImage *fimage = psImageCopy (NULL, image, PS_TYPE_F32);
+
+    int binning = atof(argv[6]);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN);
+    psImage *fimage = psImageRebin (NULL, image, NULL, 0, binning, stats);
+
+    float min = atof(argv[3]);
+    float max = atof(argv[4]);
+
+    psImageJpeg (fimage, argv[2], min, max);
+
+    psFree (header);
+    psFree (image);
+
+# endif
+
+# if (0)
+
+    psMetadata *row;
+    psArray *table;
+
+    psMetadataItem *mdi;
+
+    psMetadataConfigWrite (header, argv[2]);
+
+    // attempt to write image with NAXIS = 0
+    mdi = psMetadataLookup (header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    // create a test image
+    // psImage *tmpimage = psImageAlloc (10, 10, PS_DATA_F32);
+
+    // create a test table
+    table = psArrayAllocEmpty (10);
+
+    for (int i = 0; i < 10; i++) {
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "ROW",   PS_DATA_S32,    "", i);
+        psMetadataAdd (row, PS_LIST_TAIL, "FROW",  PS_TYPE_F32,    "", 0.1*i);
+        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY", PS_DATA_STRING, "", "test line");
+
+        table->data[i] = row;
+    }
+    table->n = 10;
+
+    psMetadata *theader = psMetadataAlloc ();
+    psMetadataAdd (theader, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING, "extension name", "SMPFILE");
+
+    psFits *fits = psFitsOpen (argv[3], "w");
+    // psFitsWriteImage (fits, header, tmpimage, 0);
+    psFitsWriteHeader (header, fits);
+    psFitsWriteTable (fits, theader, table);
+
+# endif
+
+# if (0)
+
+void psExit (int status, char *process, char *format, ...) {
+
+    va_list ap;
+
+    va_start (ap, format);
+    fprintf (stderr, "exiting %s\n", process);
+    vfprintf (stderr, format, ap);
+    va_end (ap);
+
+    exit (status);
+}
+
+# endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestArguments.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestArguments.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestArguments.c	(revision 27622)
@@ -0,0 +1,19 @@
+# include "psphotInternal.h"
+static int usage ();
+
+void psphotTestArguments (int *argc, char **argv) {
+
+  // basic pslib options
+  psLogSetFormat ("M");
+  psArgumentVerbosity (argc, argv);
+
+  if (*argc != 7) usage ();
+
+  return;
+}
+
+static int usage () {
+
+    fprintf (stderr, "USAGE: psphotTest (input.fits) (output.jpg) (zero) (scale) (colormap) (rebin)\n");
+    exit (2);
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestPSF.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestPSF.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestPSF.c	(revision 27622)
@@ -0,0 +1,131 @@
+# include "psphotInternal.h"
+
+bool psphotTestPSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool            status;
+    char           *modelName;
+    pmPSF          *psf = NULL;
+    psArray        *stars = NULL;
+
+    psTimerStart ("psphot");
+
+    psphotSaveImage (NULL, readout->image,  "image.fits");
+
+    // check if a PSF model is supplied by the user
+    psf = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.PSF");
+    if (psf != NULL) return psf;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    if (!status) {
+        NSTARS = PS_MIN (sources->n, 200);
+        psWarning("PSF_MAX_NSTARS is not set in the recipe --- defaulting to %d\n", NSTARS);
+    }
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) {
+        POISSON_ERRORS = true;
+        psWarning("POISSON_ERRORS is not set in the recipe --- defaulting to true.\n");
+    }
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // how to model the PSF variations across the field
+    // XXX make a default value?  or not?
+    psMetadata *md = psMetadataLookupMetadata (&status, recipe, "PSF.TREND.MASK");
+    psPolynomial2D *psfTrendMask;
+    if (!status || !md) {
+        psWarning("PSF.TREND.MASK is not set in the recipe --- defaulting to use zeroth order.\n");
+        psfTrendMask = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 0, 0);
+    } else {
+        psfTrendMask = psPolynomial2DfromMetadata (md);
+        if (!psfTrendMask) {
+            psError(PSPHOT_ERR_PSF, true, "Unable to construct polynomial from PSF.TREND.MASK in the recipe");
+            return false;
+        }
+    }
+
+    stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; (i < sources->n) && (stars->n < NSTARS); i++) {
+        pmSource *source = sources->data[i];
+        // if (source->mode & PM_SOURCE_MODE_PSFSTAR) psArrayAdd (stars, 200, source);
+        psArrayAdd (stars, 200, source);
+    }
+    psLogMsg ("psphot.pspsf", 4, "selected candidate %ld PSF objects\n", stars->n);
+
+    if (stars->n == 0) {
+        psError(PSPHOT_ERR_PSF, true, "Failed to find any PSF candidates");
+        return NULL;
+    }
+
+    // get the fixed PSF fit radius
+    // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    if (!status) {
+        psWarning("PSF_FIT_RADIUS is not set in the recipe --- defaulting to 20.0\n");
+        RADIUS = 20.0;
+    }
+
+    // for this test, require a single model
+    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+    if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+    if (mdi->type != PS_DATA_STRING) psAbort("choose a single PSF_MODEL");
+    modelName = mdi->data.V;
+
+    pmPSFtestModel (stars, modelName, RADIUS, POISSON_ERRORS, psfTrendMask);
+
+    psphotSaveImage (NULL, readout->image,  "resid.fits");
+    psphotSaveImage (NULL, readout->mask,   "mask.fits");
+    psphotSaveImage (NULL, readout->weight, "weight.fits");
+    
+    return true;
+}
+
+bool pmPSFtestModel (psArray *sources, char *modelName, float RADIUS, bool poissonErrors, psPolynomial2D *psfTrendMask)
+{
+    bool status;
+    float x;
+    float y;
+
+    pmModelType type = pmModelSetType (modelName);
+    pmPSF *psf = pmPSFAlloc (type, poissonErrors, psfTrendMask);
+    if (psf == NULL) psAbort("unknown model");
+
+    FILE *f = fopen ("params.dat", "w");
+
+    // stage 1:  fit an independent model (freeModel) to all sources
+    psTimerStart ("fit");
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel  *model  = pmSourceModelGuess (source, psf->type);
+        x = source->peak->x;
+        y = source->peak->y;
+
+        // set temporary object mask and fit object
+        // fit model as EXT, not PSF
+        psImageKeepCircle (source->mask, x, y, RADIUS, "OR", markVal);
+        status = pmSourceFitModel (source, model, PM_SOURCE_FIT_EXT);
+        psImageKeepCircle (source->mask, x, y, RADIUS, "AND", PS_NOT_IMAGE_MASK(markVal));
+
+	// write fitted parameters to file
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_XPOS]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_YPOS]);
+
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXX]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SYY]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXY]);
+
+	fprintf (f, "%f %d\n", model->chisq, model->nIter);
+
+	// subtract model flux
+	pmModelSub (source->pixels, source->mask, model, PM_MODEL_OP_FULL);
+    }
+    fclose (f);
+    psLogMsg ("psphot.psftest", 4, "fit ext: %f sec for %ld sources\n", psTimerMark ("fit"), sources->n);
+    return true;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestSourceOutput.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestSourceOutput.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotTestSourceOutput.c	(revision 27622)
@@ -0,0 +1,157 @@
+# include "psphotInternal.h"
+
+enum {
+    PSPHOT_ADD_NONE = 0,
+    PSPHOT_ADD_MODEL = 1,
+    PSPHOT_ADD_R0 = 2,
+    PSPHOT_ADD_R1 = 4,
+};
+
+bool psphotAddModel(psImage *image,
+		    pmModel *model,
+		    int mode
+    )
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *params = model->params;
+    psS32 imageCol;
+    psS32 imageRow;
+    psF32 skyValue = params->data.F32[0];
+    psF32 pixelValue;
+    
+    float xCenter = model->params->data.F32[PM_PAR_XPOS];
+    float yCenter = model->params->data.F32[PM_PAR_YPOS];
+    float Io = model->params->data.F32[PM_PAR_I0];
+
+    int xBin = 1;
+    int yBin = 1;
+    float xResidCenter = 0.0;
+    float yResidCenter = 0.0;
+
+    psImageInterpolateOptions *Ro = NULL;
+    psImageInterpolateOptions *Rx = NULL;
+    psImageInterpolateOptions *Ry = NULL;
+    if (model->residuals && (mode & (PSPHOT_ADD_R0 | PSPHOT_ADD_R1))) {
+	Ro = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Ro, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+	Rx = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Rx, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+	Ry = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Ry, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+
+	xBin = model->residuals->xBin;
+	yBin = model->residuals->yBin;
+	xResidCenter = model->residuals->xCenter;
+	yResidCenter = model->residuals->yCenter;
+    }
+
+    for (psS32 iy = 0; iy < image->numRows; iy++) {
+        for (psS32 ix = 0; ix < image->numCols; ix++) {
+
+            // Convert i/j to image coord space:
+	    imageCol = ix + image->col0;
+	    imageRow = iy + image->row0;
+
+            x->data.F32[0] = (float) imageCol;
+            x->data.F32[1] = (float) imageRow;
+
+            // set the appropriate pixel value for this coordinate
+	    if (mode & PSPHOT_ADD_MODEL) {
+		pixelValue = model->modelFunc (NULL, params, x) - skyValue;
+	    } else {
+		pixelValue = 0.0;
+	    }
+
+	    // get the contribution from the residual model
+	    // XXX for a test, do this for all sources and all pixels
+	    if (Ro) {
+		// fractional image position
+		// this is wrong for the 'center' case
+		float ox = xBin*(ix + 0.5 + image->col0 - xCenter) + xResidCenter;
+		float oy = yBin*(iy + 0.5 + image->row0 - yCenter) + yResidCenter;
+
+		psImageMaskType mflux = 0;
+		double Fo = 0.0;
+		double Fx = 0.0;
+		double Fy = 0.0;
+		psImageInterpolate (&Fo, NULL, &mflux, ox, oy, Ro);
+		psImageInterpolate (&Fx, NULL, &mflux, ox, oy, Rx);
+		psImageInterpolate (&Fy, NULL, &mflux, ox, oy, Ry);
+
+		if (!mflux && isfinite(Fo) && isfinite(Fx) && isfinite(Fy)) {
+		    if (mode & PSPHOT_ADD_R0) {
+			pixelValue += Io*Fo;
+		    }
+		    if (mode & PSPHOT_ADD_R1) {
+			pixelValue += Io*(xCenter*Fx + yCenter*Fy);
+		    }
+		}
+	    }
+	    image->data.F32[iy][ix] += pixelValue;
+        }
+    }
+    psFree(x);
+    psFree(Ro);
+    psFree(Rx);
+    psFree(Ry);
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return(true);
+}
+
+// construct an initial PSF model for each object 
+bool psphotTestSourceOutput (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    psImage *imMo = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    psImage *imR0 = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    psImage *imR1 = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    
+    // create template model
+    pmModel *modelRef = pmModelAlloc(psf->type);
+    modelRef->params->data.F32[PM_PAR_SKY] = 0;
+    modelRef->params->data.F32[PM_PAR_I0] = 1000;
+
+    int dx = 25;
+    int dy = 25;
+
+    // generate a grid of fake sources with amplitude 1000
+    for (int iy = 50; iy < imMo->numRows; iy += 100) {
+	for (int ix = 50; ix < imMo->numCols; ix += 100) {
+	    
+	    // assign the x and y coords to the image center
+	    modelRef->params->data.F32[PM_PAR_XPOS] = ix;
+	    modelRef->params->data.F32[PM_PAR_YPOS] = iy;
+	    
+	    // create modelPSF from this model
+	    pmModel *model = pmModelFromPSF (modelRef, psf);
+	    model->residuals = psf->residuals;
+
+	    // generate working image for this source
+	    psRegion region = {ix - dx, ix + dx, iy - dy, iy + dy};
+
+	    psImage *vM = psImageSubset (imMo, region);
+	    psImage *v0 = psImageSubset (imR0, region);
+	    psImage *v1 = psImageSubset (imR1, region);
+
+	    // we want to make one image o
+	    psphotAddModel (vM, model, PSPHOT_ADD_MODEL);
+	    psphotAddModel (v0, model, PSPHOT_ADD_R0);
+	    psphotAddModel (v1, model, PSPHOT_ADD_R1);
+	}
+    }
+
+    psphotSaveImage (NULL, imMo, "grid.Mo.fits");
+    psphotSaveImage (NULL, imR0, "grid.R0.fits");
+    psphotSaveImage (NULL, imR1, "grid.R1.fits");
+
+    exit (0);
+}
+
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotThreadTools.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotThreadTools.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotThreadTools.c	(revision 27622)
@@ -0,0 +1,116 @@
+# include "psphotInternal.h"
+
+// the strategy here is to divide the image into 2x2 blocks of cells and cycle through
+// the four discontiguous sets of cells, threading all within a set and blocking between
+// sets 
+
+// we divide the image region into 2*2 blocks of size Nx*Ny, the image will have 
+// Cx*Cy blocks so that (2Nx)Cx = numCols, (2Ny)Cy = numRows.  We want to choose Cx and
+// Cy so that (2Nx)Cx * (2Ny)Cy = 4 * NFILL * nThreads -- each of the four sets of cells
+// has enough cells to allow NFILL cells for each thread (to better distribute heavy and
+// light load cells
+    
+// the array runs from readout->image->col0 to readout->image->col0 + readout->image->numCols 
+
+// we save these for threaded analysis runs
+static int Xo = 0;
+static int Yo = 0;
+static int Nx = 1;
+static int Ny = 1;
+
+bool psphotChooseCellSizes (int *Cx, int *Cy, pmReadout *readout, int nThreads) {
+
+    int nCells = nThreads * 2*2; // number of cells in a single set
+    int C = sqrt(nCells) + 0.5;
+    
+    // we need to assign Cx and Cy based on the dimensionality of the image
+    // crude way to find most evenly balanced factors of nCells:
+    for (int i = C; i >= 1; i--) {
+	int C1 = nCells / C;
+	int C2 = nCells / C1;
+	if (C1*C2 != nCells) continue;
+
+	if (readout->image->numRows > readout->image->numCols) {
+	    *Cx = PS_MAX (C1, C2);
+	    *Cy = PS_MIN (C1, C2);
+	} else {
+	    *Cx = PS_MAX (C1, C2);
+	    *Cy = PS_MIN (C1, C2);
+	}
+
+	Xo = readout->image->col0;
+	Yo = readout->image->row0;
+	Nx = readout->image->numCols / (*Cx*2);
+	Ny = readout->image->numRows / (*Cy*2);
+
+	return true;
+    }
+    *Cx = 1;
+    *Cy = 1; 
+
+    Xo = readout->image->col0;
+    Yo = readout->image->row0;
+    Nx = readout->image->numCols / (*Cx*2);
+    Ny = readout->image->numRows / (*Cy*2);
+
+    return true;
+}
+
+
+bool psphotCoordToCell (int *group, int *cell, float x, float y, int Cx, int Cy) {
+  
+    // XXX need to handle edges
+    int ix = (x - Xo)/(2*Nx);
+    ix = PS_MAX (0, PS_MIN (ix, Cx - 1));
+
+    int iy = (y - Yo)/(2*Ny);
+    iy = PS_MAX (0, PS_MIN (iy, Cy - 1));
+
+    int jx = (((int)(x - Xo))%(2*Nx))/Nx;
+    jx = PS_MAX (0, PS_MIN (jx, Nx - 1));
+
+    int jy = (((int)(y - Yo))%(2*Ny))/Ny;
+    jy = PS_MAX (0, PS_MIN (jy, Ny - 1));
+
+    *group = jx + 2*jy;
+    *cell  = ix + Cx*iy;
+
+    return true;
+}
+
+
+// we have 2x2 * Cx*Cy cells for the image.  we need a function to convert an x,y
+// coordinate pair into the index for these cells
+
+// first, how shall we number them?  is there one index for all cells, or one set of
+// indices for each of the 2x2 cell groups?  
+
+psArray *psphotAssignSources (int Cx, int Cy, psArray *sources) {
+
+    psArray *cellGroups = psArrayAlloc (4);
+    for (int i = 0; i < cellGroups->n; i++) {
+	psArray *cells = psArrayAlloc (Cx*Cy);
+	cellGroups->data[i] = cells;
+	for (int j = 0; j < cells->n; j++) {
+	    psArray *cellSources = psArrayAllocEmpty (50);
+	    cells->data[j] = cellSources;
+	}
+    }
+
+    for (int i = 0; i < sources->n; i++) {
+    
+	int group = 0;
+	int cell = 0;
+
+	pmSource *source = sources->data[i];
+
+	psphotCoordToCell (&group, &cell, source->peak->xf, source->peak->yf, Cx, Cy);
+       
+	psArray *cells = cellGroups->data[group];
+	psArray *cellSources = cells->data[cell];
+	
+	psArrayAdd (cellSources, 100, source);
+    }
+	
+    return cellGroups;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotVersion.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotVersion.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotVersion.c	(revision 27622)
@@ -0,0 +1,126 @@
+#include "psphotInternal.h"
+#include "psphotVersionDefinitions.h"
+
+#ifdef HAVE_KAPA
+#include <kapa.h>
+#endif
+
+#ifndef PSPHOT_VERSION
+#error "PSPHOT_VERSION is not set"
+#endif
+#ifndef PSPHOT_BRANCH
+#error "PSPHOT_BRANCH is not set"
+#endif
+#ifndef PSPHOT_SOURCE
+#error "PSPHOT_SOURCE is not set"
+#endif
+
+psString psphotVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", PSPHOT_BRANCH, PSPHOT_VERSION);
+    return value;
+}
+
+psString psphotSource(void)
+{
+    return psStringCopy(PSPHOT_SOURCE);
+}
+
+psString psphotVersionLong(void)
+{
+    psString version = psphotVersion();  // Version, to return
+    psString source = psphotSource();    // Source
+
+    psStringPrepend(&version, "psphot ");
+    psStringAppend(&version, " from %s, built %s, %s", source, __DATE__, __TIME__);
+    psFree(source);
+
+#ifdef __OPTIMIZE__
+    psStringAppend(&version, " optimised");
+#else
+    psStringAppend(&version, " unoptimised");
+#endif
+
+#ifdef HAVE_KAPA
+#if 0
+    // XXX Need to get ohana and libdvo versions
+    psString ohanaVersion = psStringStripCVS(ohana_version(), "Name");
+    psString libdvoVersion = psStringStripCVS(libdvo_version(), "Name");
+    psStringAppend(&version, " with libkapa (ohana %s, libdvo: %s)", ohanaVersion, libdvoVersion);
+    psFree(ohanaVersion);
+    psFree(libdvoVersion);
+#else
+    psStringAppend(&version, " with libkapa");
+#endif
+
+#else
+    psStringAppend (&version, " without libkapa");
+#endif
+
+    return version;
+}
+
+bool psphotVersionHeader(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    psString version = psphotVersion(); // Software version
+    psString source = psphotSource();   // Software source
+
+    psStringPrepend(&version, "psphot version: ");
+    psStringPrepend(&source, "psphot source: ");
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, version);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, source);
+
+    psFree(version);
+    psFree(source);
+
+    return true;
+}
+
+
+bool psphotVersionHeaderFull(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psString history = NULL;               // History string
+    psStringAppend(&history, "psphot at %s", timeString);
+    psFree(timeString);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+    psFree(history);
+
+    psLibVersionHeader(header);
+    psModulesVersionHeader(header);
+    psphotVersionHeader(header);
+
+    return true;
+}
+
+
+void psphotVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("psphot", PS_LOG_INFO, "psphot at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString psphot = psphotVersionLong(); // psphot version
+
+    psLogMsg("psphot", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("psphot", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("psphot", PS_LOG_INFO, "%s", psphot);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(psphot);
+
+    return;
+}
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotVersionDefinitions.h.in
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotVersionDefinitions.h.in	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotVersionDefinitions.h.in	(revision 27622)
@@ -0,0 +1,8 @@
+#ifndef PSPHOT_VERSION_DEFINITIONS_H
+#define PSPHOT_VERSION_DEFINITIONS_H
+
+#define PSPHOT_VERSION @PSPHOT_VERSION@ // SVN version
+#define PSPHOT_BRANCH  @PSPHOT_BRANCH@  // SVN branch
+#define PSPHOT_SOURCE  @PSPHOT_SOURCE@  // SVN source
+
+#endif
Index: /branches/eam_branches/stackphot.20100406/psphot/src/psphotVisual.c
===================================================================
--- /branches/eam_branches/stackphot.20100406/psphot/src/psphotVisual.c	(revision 27622)
+++ /branches/eam_branches/stackphot.20100406/psphot/src/psphotVisual.c	(revision 27622)
@@ -0,0 +1,2389 @@
+# include "psphotInternal.h"
+
+// this function displays representative images as the psphot analysis progresses:
+// 0 : image, 1 : variance
+// 0 : backsub, 1 : variance, 2 : backgnd
+// 0 : backsub, 1 : variance, 2 : signif
+// (overlay peaks on images)
+// (overlay footprints on images)
+// (overlay moments on images)
+// (overlay rough class on images)
+// 0 : backsub, 1 : psfpos, 2: psfsub
+// 0 : backsub, 1 : lin_resid, 2: psfsub
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+
+bool pmVisualLimitsFromVectors (Graphdata *graphdata, psVector *xVec, psVector *yVec);
+
+// functions used to visualize the analysis as it goes
+// these are invoked by the -visual options
+
+static int kapa1 = -1;
+static int kapa2 = -1;
+static int kapa3 = -1;
+
+int psphotKapaChannel (int channel) {
+
+    switch (channel) {
+      case 1:
+        if (kapa1 == -1) {
+            kapa1 = KapaOpenNamedSocket ("kapa", "psphot:images");
+            if (kapa1 == -1) {
+                fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+                pmVisualSetVisual(false);
+            }
+        }
+        return kapa1;
+      case 2:
+        if (kapa2 == -1) {
+            kapa2 = KapaOpenNamedSocket ("kapa", "psphot:plots");
+            if (kapa2 == -1) {
+                fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+                pmVisualSetVisual(false);
+            }
+        }
+        return kapa2;
+      case 3:
+        if (kapa3 == -1) {
+            kapa3 = KapaOpenNamedSocket ("kapa", "psphot:stamps");
+            if (kapa3 == -1) {
+                fprintf (stderr, "failure to open kapa; visual mode disabled\n");
+                pmVisualSetVisual(false);
+            }
+        }
+        return kapa3;
+      default:
+        psAbort ("unknown kapa channel");
+    }
+    psAbort ("unknown kapa channel");
+}
+
+bool psphotVisualEraseOverlays (int channel, char *overlay) {
+
+    int myKapa = psphotKapaChannel (channel);
+    if (!(strcasecmp (overlay, "all"))) {
+      KiiEraseOverlay (myKapa, "red");
+      KiiEraseOverlay (myKapa, "green");
+      KiiEraseOverlay (myKapa, "blue");
+      KiiEraseOverlay (myKapa, "yellow");
+      return true;
+    }
+    KiiEraseOverlay (myKapa, overlay);
+    return true;
+}
+
+bool psphotVisualShowMask (int kapaFD, psImage *inImage, const char *name, int channel) {
+
+    KiiImage image;
+    KapaImageData data;
+    Coords coords;
+
+    strcpy (coords.ctype, "RA---TAN");
+
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS);
+    if (!psImageBackground(stats, NULL, inImage, NULL, 0, rng)) {
+        fprintf (stderr, "failed to get background values\n");
+        return false;
+    }
+
+    image.Nx = inImage->numCols;
+    image.Ny = inImage->numRows;
+
+    ALLOCATE (image.data2d, float *, image.Ny);
+    for (int iy = 0; iy < image.Ny; iy++) {
+        ALLOCATE (image.data2d[iy], float, image.Nx);
+        for (int ix = 0; ix < image.Nx; ix++) {
+            image.data2d[iy][ix] = inImage->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix];
+        }
+    }
+
+    strcpy (data.name, name);
+    strcpy (data.file, name);
+    data.zero = -1;
+    data.range = 32;
+    data.logflux = 0;
+
+    KiiSetChannel (kapaFD, channel);
+    KiiNewPicture2D (kapaFD, &image, &data, &coords);
+
+    for (int iy = 0; iy < image.Ny; iy++) {
+        free (image.data2d[iy]);
+    }
+    free (image.data2d);
+
+    psFree (stats);
+    psFree (rng);
+
+    return true;
+}
+
+bool psphotVisualScaleImage (int kapaFD, psImage *inImage, psImage *inMask, const char *name, int channel) {
+
+    KiiImage image;
+    KapaImageData data;
+    Coords coords;
+
+    strcpy (coords.ctype, "RA---TAN");
+
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS);
+    if (!psImageBackground(stats, NULL, inImage, inMask, 0xffff, rng)) {
+        fprintf (stderr, "failed to get background values\n");
+        return false;
+    }
+
+    image.data2d = inImage->data.F32;
+    image.Nx = inImage->numCols;
+    image.Ny = inImage->numRows;
+
+    strcpy (data.name, name);
+    strcpy (data.file, name);
+    data.zero = stats->robustMedian - stats->robustStdev;
+    data.range = 5*stats->robustStdev;
+    data.logflux = 0;
+
+    KiiSetChannel (kapaFD, channel);
+    KiiNewPicture2D (kapaFD, &image, &data, &coords);
+
+    psFree (stats);
+    psFree (rng);
+
+    return true;
+}
+
+bool psphotVisualRangeImage (int kapaFD, psImage *inImage, const char *name, int channel, float min, float max) {
+
+    KiiImage image;
+    KapaImageData data;
+    Coords coords;
+
+    strcpy (coords.ctype, "RA---TAN");
+
+    image.data2d = inImage->data.F32;
+    image.Nx = inImage->numCols;
+    image.Ny = inImage->numRows;
+
+    strcpy (data.name, name);
+    strcpy (data.file, name);
+    data.zero = min;
+    data.range = max - min;
+    data.logflux = 0;
+
+    KiiSetChannel (kapaFD, channel);
+    KiiNewPicture2D (kapaFD, &image, &data, &coords);
+
+    return true;
+}
+
+bool psphotVisualShowImage (pmReadout *readout) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    int kapa = psphotKapaChannel (1);
+    if (kapa == -1) return false;
+
+    psphotVisualShowMask (kapa, readout->mask, "mask", 2);
+    psphotVisualScaleImage (kapa, readout->variance, readout->mask, "variance", 1);
+    psphotVisualScaleImage (kapa, readout->image, readout->mask, "image", 0);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowBackground (pmConfig *config, const pmFPAview *view, pmReadout *readout) {
+
+    pmReadout *backgnd;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int kapa = psphotKapaChannel (1);
+    if (kapa == -1) return false;
+
+    bool status = false;
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        backgnd = file->readout;
+    } else {
+        backgnd = pmFPAviewThisReadout (view, file->fpa);
+    }
+
+    psphotVisualScaleImage (kapa, backgnd->image, readout->mask, "backgnd", 2);
+    psphotVisualScaleImage (kapa, readout->image, readout->mask, "backsub", 0);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowSignificance (psImage *image, float min, float max) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    int kapa = psphotKapaChannel (1);
+    if (kapa == -1) return false;
+
+    psphotVisualRangeImage (kapa, image, "signif", 2, min, max);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowPeaks (pmDetections *detections) {
+
+    int Noverlay;
+    KiiOverlay *overlay;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int kapa = psphotKapaChannel (1);
+    if (kapa == -1) return false;
+
+    psArray *peaks = detections->peaks;
+
+    // note: this uses the Ohana allocation tools:
+    // ALLOCATE (overlay, KiiOverlay, 3*peaks->n + 1);
+    ALLOCATE (overlay, KiiOverlay, peaks->n + 2);
+
+    Noverlay = 0;
+    for (int i = 0; i < peaks->n; i++) {
+
+        pmPeak *peak = peaks->data[i];
+        if (peak == NULL) continue;
+
+        overlay[Noverlay].type = KII_OVERLAY_BOX;
+        overlay[Noverlay].x = peak->xf;
+        overlay[Noverlay].y = peak->yf;
+        overlay[Noverlay].dx = 2.0;
+        overlay[Noverlay].dy = 2.0;
+        overlay[Noverlay].angle = 0.0;
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+    }
+
+    KiiLoadOverlay (kapa, overlay, Noverlay, "red");
+    FREE (overlay);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowFootprints (pmDetections *detections) {
+
+    int Noverlay;
+    KiiOverlay *overlay;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int kapa = psphotKapaChannel (1);
+    if (kapa == -1) return false;
+
+    psArray *footprints = detections->footprints;
+    if (!footprints) return true;
+
+    // note: this uses the Ohana allocation tools:
+    int NOVERLAY = footprints->n;
+    ALLOCATE (overlay, KiiOverlay, NOVERLAY);
+
+    Noverlay = 0;
+    for (int i = 0; i < footprints->n; i++) {
+
+        pmSpan *span = NULL;
+
+        pmFootprint *footprint = footprints->data[i];
+        if (footprint == NULL) continue;
+        if (footprint->spans == NULL) continue;
+        if (footprint->spans->n < 1) continue;
+
+        // draw the top
+        // XXX need to allow top (and bottom) to have more than one span
+        span = footprint->spans->data[0];
+        overlay[Noverlay].type = KII_OVERLAY_LINE;
+        overlay[Noverlay].x = span->x0;
+        overlay[Noverlay].y = span->y;
+        overlay[Noverlay].dx = span->x1 - span->x0;
+        overlay[Noverlay].dy = 0;
+        overlay[Noverlay].angle = 0.0;
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+        CHECK_REALLOCATE (overlay, KiiOverlay, NOVERLAY, Noverlay, 100);
+
+        int ys = span->y;
+        int x0s = span->x0;
+        int x1s = span->x1;
+
+        // draw the outer span edges
+        for (int j = 1; j < footprint->spans->n; j++) {
+            pmSpan *span1 = footprint->spans->data[j];
+
+            int ye = span1->y;
+            int x0e = span1->x0;
+            int x1e = span1->x1;
+
+            // we cannot have two discontinuous spans on the top or bottom, right? (no, probably not right)
+            // find all of the spans in this row and generate x0e, x01:
+            for (int k = j + 1; k < footprint->spans->n; k++) {
+                pmSpan *span2 = footprint->spans->data[k];
+                if (span2->y > span1->y) break;
+                x0e = PS_MIN (x0e, span2->x0);
+                x1e = PS_MAX (x1e, span2->x1);
+                j++;
+            }
+
+            overlay[Noverlay].type = KII_OVERLAY_LINE;
+            overlay[Noverlay].x = x0s;
+            overlay[Noverlay].y = ys;
+            overlay[Noverlay].dx = x0e - x0s;
+            overlay[Noverlay].dy = ye - ys;
+            overlay[Noverlay].angle = 0.0;
+            overlay[Noverlay].text = NULL;
+            Noverlay ++;
+            CHECK_REALLOCATE (overlay, KiiOverlay, NOVERLAY, Noverlay, 100);
+
+            overlay[Noverlay].type = KII_OVERLAY_LINE;
+            overlay[Noverlay].x = x1s;
+            overlay[Noverlay].y = ys;
+            overlay[Noverlay].dx = x1e - x1s;
+            overlay[Noverlay].dy = ye - ys;
+            overlay[Noverlay].angle = 0.0;
+            overlay[Noverlay].text = NULL;
+            Noverlay ++;
+            CHECK_REALLOCATE (overlay, KiiOverlay, NOVERLAY, Noverlay, 100);
+
+            ys = ye;
+            x0s = x0e;
+            x1s = x1e;
+        }
+
+        // draw the bottom
+        span = footprint->spans->data[footprint->spans->n - 1];
+        overlay[Noverlay].type = KII_OVERLAY_LINE;
+        overlay[Noverlay].x = span->x0;
+        overlay[Noverlay].y = span->y;
+        overlay[Noverlay].dx = span->x1 - span->x0;
+        overlay[Noverlay].dy = 0;
+        overlay[Noverlay].angle = 0.0;
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+        CHECK_REALLOCATE (overlay, KiiOverlay, NOVERLAY, Noverlay, 100);
+    }
+
+    KiiLoadOverlay (kapa, overlay, Noverlay, "blue");
+    FREE (overlay);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowMoments (psArray *sources) {
+
+    int Noverlay;
+    KiiOverlay *overlay;
+
+    psEllipseMoments emoments;
+    psEllipseAxes axes;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int kapa = psphotKapaChannel (1);
+    if (kapa == -1) return false;
+
+    // XXX mark the different source classes with different color/shape dots
+    // XXX are moments S/N and peak S/N consistent?
+
+    // note: this uses the Ohana allocation tools:
+    ALLOCATE (overlay, KiiOverlay, sources->n);
+
+    Noverlay = 0;
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        if (source == NULL) continue;
+
+        pmMoments *moments = source->moments;
+        if (moments == NULL) continue;
+
+        overlay[Noverlay].type = KII_OVERLAY_CIRCLE;
+        overlay[Noverlay].x = moments->Mx;
+        overlay[Noverlay].y = moments->My;
+
+        emoments.x2 = moments->Mxx;
+        emoments.xy = moments->Mxy;
+        emoments.y2 = moments->Myy;
+
+        axes = psEllipseMomentsToAxes (emoments, 20.0);
+
+        overlay[Noverlay].dx = 2.0*axes.major;
+        overlay[Noverlay].dy = 2.0*axes.minor;
+
+        overlay[Noverlay].angle = axes.theta * PS_DEG_RAD;
+
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+    }
+
+    KiiLoadOverlay (kapa, overlay, Noverlay, "yellow");
+    FREE (overlay);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualPlotMoments (psMetadata *recipe, psMetadata *analysis, psArray *sources) {
+
+    bool status;
+    Graphdata graphdata;
+    KapaSection section;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (2);
+    if (myKapa == -1) return false;
+
+    KapaClearPlots (myKapa);
+    KapaInitGraph (&graphdata);
+    KapaSetFont (myKapa, "courier", 14);
+
+    float SN_LIM = psMetadataLookupF32(&status, recipe, "PSF_SN_LIM");
+
+    // select the max psfX,Y values for the plot limits
+    float Xmin = 1000.0, Xmax = 0.0;
+    float Ymin = 1000.0, Ymax = 0.0;
+    {
+        int nRegions = psMetadataLookupS32 (&status, analysis, "PSF.CLUMP.NREGIONS");
+        for (int n = 0; n < nRegions; n++) {
+
+            char regionName[64];
+            snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", n);
+            psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+
+            float psfX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");
+            float psfY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");
+            float psfdX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");
+            float psfdY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");
+
+            float X0 = psfX - 4.0*psfdX;
+            float X1 = psfX + 4.0*psfdX;
+            float Y0 = psfY - 4.0*psfdY;
+            float Y1 = psfY + 4.0*psfdY;
+
+            if (isfinite(X0)) { Xmin = PS_MIN(Xmin, X0); }
+            if (isfinite(X1)) { Xmax = PS_MAX(Xmax, X1); }
+            if (isfinite(Y0)) { Ymin = PS_MIN(Ymin, Y0); }
+            if (isfinite(Y1)) { Ymax = PS_MAX(Ymax, Y1); }
+        }
+    }
+    Xmin = PS_MAX(Xmin, -0.1);
+    Ymin = PS_MAX(Ymin, -0.1);
+
+    // XXX test: hardwire plot limits
+    // Xmin = -0.1; Ymin = -0.1;
+    // Xmax = 20.1; Ymax = 20.1;
+
+    // storage vectors for data to be plotted
+    psVector *xBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *xFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // construct the vectors
+    int nB = 0;
+    int nF = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->moments == NULL)
+            continue;
+
+        xFaint->data.F32[nF] = source->moments->Mxx;
+        yFaint->data.F32[nF] = source->moments->Myy;
+        mFaint->data.F32[nF] = -2.5*log10(source->moments->Sum);
+        nF++;
+
+        // XXX make this a user-defined cutoff
+        if (source->moments->SN < SN_LIM)
+            continue;
+
+        xBright->data.F32[nB] = source->moments->Mxx;
+        yBright->data.F32[nB] = source->moments->Myy;
+        mBright->data.F32[nB] = -2.5*log10(source->moments->Sum);
+        nB++;
+    }
+    xFaint->n = nF;
+    yFaint->n = nF;
+    mFaint->n = nF;
+
+    xBright->n = nB;
+    yBright->n = nB;
+    mBright->n = nB;
+
+    // three sections: MxxMyy, MagMxx, MagMyy
+
+    // first section: MxxMyy
+    section.dx = 0.75;
+    section.dy = 0.75;
+    section.x  = 0.00;
+    section.y  = 0.00;
+    section.name = psStringCopy ("MxxMyy");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = Xmin;
+    graphdata.ymin = Ymin;
+    graphdata.xmax = Xmax;
+    graphdata.ymax = Ymax;
+    KapaSetLimits (myKapa, &graphdata);
+
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "M_xx| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "M_yy| (pixels)", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nF, &graphdata);
+    KapaPlotVector (myKapa, nF, xFaint->data.F32, "x");
+    KapaPlotVector (myKapa, nF, yFaint->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nB, &graphdata);
+    KapaPlotVector (myKapa, nB, xBright->data.F32, "x");
+    KapaPlotVector (myKapa, nB, yBright->data.F32, "y");
+
+    // second section: MagMyy
+    section.dx = 0.75;
+    section.dy = 0.25;
+    section.x  = 0.00;
+    section.y  = 0.80;
+    section.name = psStringCopy ("MagMyy");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = -17.1;
+    graphdata.xmax =  -7.9;
+    graphdata.ymin = Ymin;
+    graphdata.ymax = Ymax;
+    KapaSetLimits (myKapa, &graphdata);
+
+    strcpy (graphdata.labels, "0210");
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "inst mag", KAPA_LABEL_XP);
+    KapaSendLabel (myKapa, "M_yy| (pixels)", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nF, &graphdata);
+    KapaPlotVector (myKapa, nF, mFaint->data.F32, "x");
+    KapaPlotVector (myKapa, nF, yFaint->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nB, &graphdata);
+    KapaPlotVector (myKapa, nB, mBright->data.F32, "x");
+    KapaPlotVector (myKapa, nB, yBright->data.F32, "y");
+
+    // third section: MagMxx
+    section.dx = 0.25;
+    section.dy = 0.75;
+    section.x  = 0.80;
+    section.y  = 0.00;
+    section.name = psStringCopy ("MagMxx");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = Xmin;
+    graphdata.xmax = Xmax;
+    graphdata.ymin =  -7.9;
+    graphdata.ymax = -17.1;
+    KapaSetLimits (myKapa, &graphdata);
+
+    strcpy (graphdata.labels, "2001");
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "M_xx| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "inst mag", KAPA_LABEL_YP);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nF, &graphdata);
+    KapaPlotVector (myKapa, nF, xFaint->data.F32, "x");
+    KapaPlotVector (myKapa, nF, mFaint->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nB, &graphdata);
+    KapaPlotVector (myKapa, nB, xBright->data.F32, "x");
+    KapaPlotVector (myKapa, nB, mBright->data.F32, "y");
+
+    // draw N circles to outline the clumps
+    {
+        KapaSelectSection (myKapa, "MxxMyy");
+
+        // draw a circle centered on psfX,Y with size of the psf limit
+        psVector *xLimit  = psVectorAlloc (120, PS_TYPE_F32);
+        psVector *yLimit  = psVectorAlloc (120, PS_TYPE_F32);
+
+        int nRegions = psMetadataLookupS32 (&status, analysis, "PSF.CLUMP.NREGIONS");
+        float PSF_CLUMP_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_CLUMP_NSIGMA");
+
+        graphdata.color = KapaColorByName ("blue");
+        graphdata.style = 0;
+
+        graphdata.xmin = Xmin;
+        graphdata.ymin = Ymin;
+        graphdata.xmax = Xmax;
+        graphdata.ymax = Ymax;
+        KapaSetLimits (myKapa, &graphdata);
+
+        for (int n = 0; n < nRegions; n++) {
+
+            char regionName[64];
+            snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", n);
+            psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+
+            float psfX  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");
+            float psfY  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");
+            float psfdX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");
+            float psfdY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");
+            float Rx = psfdX * PSF_CLUMP_NSIGMA;
+            float Ry = psfdY * PSF_CLUMP_NSIGMA;
+
+            for (int i = 0; i < xLimit->n; i++) {
+                xLimit->data.F32[i] = Rx*cos(i*2.0*M_PI/120.0) + psfX;
+                yLimit->data.F32[i] = Ry*sin(i*2.0*M_PI/120.0) + psfY;
+            }
+            KapaPrepPlot (myKapa, xLimit->n, &graphdata);
+            KapaPlotVector (myKapa, xLimit->n, xLimit->data.F32, "x");
+            KapaPlotVector (myKapa, yLimit->n, yLimit->data.F32, "y");
+        }
+        psFree (xLimit);
+        psFree (yLimit);
+    }
+
+    psFree (xBright);
+    psFree (yBright);
+    psFree (mBright);
+    psFree (xFaint);
+    psFree (yFaint);
+    psFree (mFaint);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+// assumes 'kapa' value is checked and set
+bool psphotVisualShowRoughClass_Single (int myKapa, psArray *sources, pmSourceType type, pmSourceMode mode, char *color) {
+
+    int Noverlay;
+    KiiOverlay *overlay;
+
+    psEllipseMoments emoments;
+    psEllipseAxes axes;
+
+    // note: this uses the Ohana allocation tools:
+    ALLOCATE (overlay, KiiOverlay, sources->n);
+
+    Noverlay = 0;
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        if (source == NULL) continue;
+
+        if (source->type != type) continue;
+        if (mode && !(source->mode & mode)) continue;
+
+        pmMoments *moments = source->moments;
+        if (moments == NULL) continue;
+
+        overlay[Noverlay].type = KII_OVERLAY_CIRCLE;
+        overlay[Noverlay].x = moments->Mx;
+        overlay[Noverlay].y = moments->My;
+
+        emoments.x2 = moments->Mxx;
+        emoments.y2 = moments->Myy;
+        emoments.xy = moments->Mxy;
+
+        axes = psEllipseMomentsToAxes (emoments, 20.0);
+
+        overlay[Noverlay].dx = 2.0*axes.major;
+        overlay[Noverlay].dy = 2.0*axes.minor;
+        overlay[Noverlay].angle = axes.theta * PS_DEG_RAD;
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+    }
+
+    KiiLoadOverlay (myKapa, overlay, Noverlay, color);
+    FREE (overlay);
+
+    return true;
+}
+
+bool psphotVisualShowRoughClass (psArray *sources) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (1);
+    if (myKapa == -1) return false;
+
+    KiiEraseOverlay (myKapa, "yellow"); // moments
+
+    psphotVisualShowRoughClass_Single (myKapa, sources, PM_SOURCE_TYPE_STAR, 0, "red");
+    psphotVisualShowRoughClass_Single (myKapa, sources, PM_SOURCE_TYPE_EXTENDED, 0, "blue");
+    psphotVisualShowRoughClass_Single (myKapa, sources, PM_SOURCE_TYPE_DEFECT, 0, "blue");
+    psphotVisualShowRoughClass_Single (myKapa, sources, PM_SOURCE_TYPE_SATURATED, 0, "red");
+    psphotVisualShowRoughClass_Single (myKapa, sources, PM_SOURCE_TYPE_STAR, PM_SOURCE_MODE_PSFSTAR, "yellow");
+    psphotVisualShowRoughClass_Single (myKapa, sources, PM_SOURCE_TYPE_STAR, PM_SOURCE_MODE_SATSTAR, "green");
+
+    fprintf (stdout, "red: STAR or SAT AREA; blue: EXTENDED or DEFECT; green: SATSTAR; yellow: PSFSTAR\n");
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowPSFModel (pmReadout *readout, pmPSF *psf) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (3);
+    if (myKapa == -1) return false;
+
+    int DX = 64;
+    int DY = 64;
+
+    psImage *psfMosaic = psImageAlloc (5*DX, 5*DY, PS_TYPE_F32);
+    psImageInit (psfMosaic, 0.0);
+
+    psImage *funMosaic = psImageAlloc (5*DX, 5*DY, PS_TYPE_F32);
+    psImageInit (funMosaic, 0.0);
+
+    psImage *resMosaic = psImageAlloc (5*DX, 5*DY, PS_TYPE_F32);
+    psImageInit (resMosaic, 0.0);
+
+    pmModel *modelRef = pmModelAlloc(psf->type);
+
+    // generate a fake model at each of the 3x3 image grid positions
+    for (int x = -2; x <= +2; x ++) {
+        for (int y = -2; y <= +2; y ++) {
+            // use the center of the center pixel of the image
+            float xc = (int)((0.5 + 0.225*x)*readout->image->numCols) + readout->image->col0 + 0.5;
+            float yc = (int)((0.5 + 0.225*y)*readout->image->numRows) + readout->image->row0 + 0.5;
+
+            // assign the x and y coords to the image center
+            // create an object with center intensity of 1000
+            modelRef->params->data.F32[PM_PAR_SKY] = 0;
+            modelRef->params->data.F32[PM_PAR_I0] = 1000;
+            modelRef->params->data.F32[PM_PAR_XPOS] = xc;
+            modelRef->params->data.F32[PM_PAR_YPOS] = yc;
+
+            // create modelPSF from this model
+            pmModel *model = pmModelFromPSF (modelRef, psf);
+            if (!model) continue;
+
+            // place the reference object in the image center
+            // no need to mask the source here
+            // XXX should we measure this for the analytical model only or the full model?
+            pmModelAddWithOffset (psfMosaic, NULL, model, PM_MODEL_OP_FULL | PM_MODEL_OP_CENTER, 0, x*DX, y*DY);
+            pmModelAddWithOffset (funMosaic, NULL, model, PM_MODEL_OP_FUNC | PM_MODEL_OP_CENTER, 0, x*DX, y*DY);
+            pmModelAddWithOffset (resMosaic, NULL, model, PM_MODEL_OP_RES0 | PM_MODEL_OP_RES1 | PM_MODEL_OP_CENTER, 0, x*DX, y*DY);
+            psFree (model);
+        }
+    }
+
+    psImage *psfLogFlux = (psImage *) psUnaryOp (NULL, psfMosaic, "log");
+    psphotVisualRangeImage (myKapa, psfLogFlux, "psf_mosaic",    0, -2.0, 3.0);
+    psphotVisualRangeImage (myKapa, funMosaic, "psf_analytical", 1, -10.0, 100.0);
+    psphotVisualRangeImage (myKapa, resMosaic, "psf_residual",   2, -10.0, 100.0);
+
+    psFree (psfMosaic);
+    psFree (funMosaic);
+    psFree (resMosaic);
+    psFree (psfLogFlux);
+    psFree (modelRef);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowPSFStars (psMetadata *recipe, pmPSF *psf, psArray *sources) {
+
+    bool status;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (3);
+    if (myKapa == -1) return false;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 21;
+    int DY = 21;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;                         // starting corner of next box
+    int dY = 0;                         // height of row so far
+    int NX = 20*DX;                     // full width of output image
+    int NY = 0;                         // total height of output image
+
+    // first, examine the PSF stars:
+    // - determine bounding boxes for summary image
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        keep |= (source->mode & PM_SOURCE_MODE_PSFSTAR);
+        if (!keep) continue;
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (dX + DX > NX) {
+            // too wide for the rest of this row
+            if (dX == 0) {
+                // alone on this row
+                NY += DY;
+                dX = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                NY += dY;
+                dX = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            dX += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+    NY += DY;
+
+    // allocate output image
+    psImage *outpos = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImage *outsub = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImageInit (outpos, 0.0);
+    psImageInit (outsub, 0.0);
+
+    int Xo = 0;                         // starting corner of next box
+    int Yo = 0;                         // starting corner of next box
+    dY = 0;                             // height of row so far
+
+    int nPSF = 0;
+
+    // next, examine the PSF stars:
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+            nPSF ++;
+            keep = true;
+        }
+        if (!keep) continue;
+
+        if (Xo + DX > NX) {
+            // too wide for the rest of this row
+            if (Xo == 0) {
+                // place source alone on this row
+                bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+                if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+                pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+
+                if (!subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+                Yo += DY;
+                Xo = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                Yo += dY;
+                Xo = 0;
+
+                bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+                if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+                pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+
+                if (!subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+                Xo = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+            if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+            psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY, true);
+
+            pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+            psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY, true);
+            if (!subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+            Xo += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    psphotVisualRangeImage (myKapa, outpos, "psfpos", 0, -0.05, 0.95);
+    psphotVisualRangeImage (myKapa, outsub, "psfsub", 1, -0.05, 0.95);
+
+    pmVisualAskUser(NULL);
+    psFree (outpos);
+    psFree (outsub);
+
+
+    // after displaying (as an image) the psf stars, we cycle throught them and display their
+    // radial profiles:
+    psphotVisualPlotRadialProfiles (recipe, sources);
+
+    return true;
+}
+
+bool psphotVisualShowSatStars (psMetadata *recipe, pmPSF *psf, psArray *sources) {
+
+    bool status;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (3);
+    if (myKapa == -1) return false;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 41;
+    int DY = 41;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;                         // starting corner of next box
+    int dY = 0;                         // height of row so far
+    int NX = 10*DX;                     // full width of output image
+    int NY = 0;                         // total height of output image
+
+    // first, examine the PSF and SAT stars:
+    // - determine bounding boxes for summary image
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        // only show "real" saturated stars (not defects)
+        if (!(source->mode & PM_SOURCE_MODE_SATSTAR)) continue;;
+        if (source->mode & PM_SOURCE_MODE_DEFECT) continue;;
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (dX + DX > NX) {
+            // too wide for the rest of this row
+            if (dX == 0) {
+                // alone on this row
+                NY += DY;
+                dX = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                NY += dY;
+                dX = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            dX += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+    NY += DY;
+
+    // allocate output image
+    psImage *outsat = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImageInit (outsat, 0.0);
+
+    int Xo = 0;                         // starting corner of next box
+    int Yo = 0;                         // starting corner of next box
+    dY = 0;                             // height of row so far
+
+    int nSAT = 0;
+
+    // next, examine the SAT stars:
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        // only show "real" saturated stars (not defects)
+        if (!(source->mode & PM_SOURCE_MODE_SATSTAR)) continue;;
+        if (source->mode & PM_SOURCE_MODE_DEFECT) continue;;
+        nSAT ++;
+
+        if (Xo + DX > NX) {
+            // too wide for the rest of this row
+            if (Xo == 0) {
+                // place source alone on this row
+                bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+                if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outsat, source, Xo, Yo, DX, DY, false);
+                if (subtracted) pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+                Yo += DY;
+                Xo = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                Yo += dY;
+                Xo = 0;
+
+                bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+                if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+                psphotMosaicSubimage (outsat, source, Xo, Yo, DX, DY, false);
+                if (subtracted) pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+                Xo = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+            if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+            psphotMosaicSubimage (outsat, source, Xo, Yo, DX, DY, false);
+            if (subtracted) pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+            Xo += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    psphotVisualScaleImage (myKapa, outsat, NULL, "satstar", 2);
+
+    pmVisualAskUser(NULL);
+    psFree (outsat);
+    return true;
+}
+
+static void plotline (int myKapa, Graphdata *graphdata, float x0, float y0, float x1, float y1) 
+{
+    float x[2], y[2];
+    x[0] = x0;
+    x[1] = x1;
+    y[0] = y0;
+    y[1] = y1;
+    KapaPrepPlot   (myKapa, 2, graphdata);
+    KapaPlotVector (myKapa, 2, x, "x");
+    KapaPlotVector (myKapa, 2, y, "y");
+}
+
+bool psphotVisualPlotRadialProfile (int myKapa, pmSource *source, psImageMaskType maskVal) {
+
+    Graphdata graphdata;
+
+    bool subtracted = (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED);
+    if (subtracted) pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+
+    int nPts = source->pixels->numRows * source->pixels->numCols;
+    psVector *rg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *Rg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *rb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *Rb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+
+    int ng = 0;
+    int nb = 0;
+    float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS] - source->pixels->col0;
+    float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS] - source->pixels->row0;
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+        for (int ix = 0; ix < source->pixels->numCols; ix++) {
+            if (source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix]) {
+                rb->data.F32[nb] = hypot (ix + 0.5 - Xo, iy + 0.5 - Yo) ;
+                // rb->data.F32[nb] = hypot (ix - Xo, iy - Yo) ;
+                Rb->data.F32[nb] = log10(rb->data.F32[nb]);
+                fb->data.F32[nb] = log10(source->pixels->data.F32[iy][ix]);
+                nb++;
+            } else {
+                rg->data.F32[ng] = hypot (ix + 0.5 - Xo, iy + 0.5 - Yo) ;
+                // rg->data.F32[ng] = hypot (ix - Xo, iy - Yo) ;
+                Rg->data.F32[ng] = log10(rg->data.F32[ng]);
+                fg->data.F32[ng] = log10(source->pixels->data.F32[iy][ix]);
+                ng++;
+            }
+        }
+    }
+
+    // generate model profiles (major and minor axis):
+    // create a model with theta = 0.0 so major and minor axes are equiv to x and y:
+    psEllipseShape rawShape, rotShape;
+
+    rawShape.sx  = source->modelPSF->params->data.F32[PM_PAR_SXX] / M_SQRT2;
+    rawShape.sy  = source->modelPSF->params->data.F32[PM_PAR_SYY] / M_SQRT2;
+    rawShape.sxy = source->modelPSF->params->data.F32[PM_PAR_SXY];
+
+    psEllipseAxes axes = psEllipseShapeToAxes (rawShape, 20.0);
+
+    axes.theta = 0.0;
+
+    rotShape = psEllipseAxesToShape (axes);
+
+    psVector *params = psVectorAlloc(source->modelPSF->params->n, PS_TYPE_F32);
+    for (int i = 0; i < source->modelPSF->params->n; i++) {
+	params->data.F32[i] = source->modelPSF->params->data.F32[i];
+    }
+    params->data.F32[PM_PAR_SXX] = rotShape.sx * M_SQRT2;
+    params->data.F32[PM_PAR_SYY] = rotShape.sy * M_SQRT2;
+    params->data.F32[PM_PAR_SXY] = rotShape.sxy;
+    params->data.F32[PM_PAR_XPOS] = 0.0;
+    params->data.F32[PM_PAR_YPOS] = 0.0;
+
+    psVector *rmod = psVectorAlloc(300, PS_TYPE_F32);
+    psVector *fmaj = psVectorAlloc(300, PS_TYPE_F32);
+    psVector *fmin = psVectorAlloc(300, PS_TYPE_F32);
+
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    float r = 0.0;
+    for (int i = 0; i < rmod->n; i++) {
+	r = i*0.1;
+	rmod->data.F32[i] = r;
+
+	coord->data.F32[1] = r;
+	coord->data.F32[0] = 0.0;
+	fmaj->data.F32[i] = log10(source->modelPSF->modelFunc (NULL, params, coord));
+
+	coord->data.F32[0] = r;
+	coord->data.F32[1] = 0.0;
+	fmin->data.F32[i] = log10(source->modelPSF->modelFunc (NULL, params, coord));
+    }
+    psFree (coord);
+    psFree (params);
+
+    float FWHM_MAJOR = 2.0*source->modelPSF->modelRadius (source->modelPSF->params, 0.5*source->modelPSF->params->data.F32[PM_PAR_I0]);
+    float FWHM_MINOR = FWHM_MAJOR * (axes.minor / axes.major);
+    if (FWHM_MAJOR < FWHM_MINOR) PS_SWAP (FWHM_MAJOR, FWHM_MINOR); 
+
+    psEllipseMoments emoments;
+    emoments.x2 = source->moments->Mxx;
+    emoments.xy = source->moments->Mxy;
+    emoments.y2 = source->moments->Myy;
+    axes = psEllipseMomentsToAxes (emoments, 20.0);
+    float MOMENTS_MAJOR = 2.355*axes.major;
+    float MOMENTS_MINOR = 2.355*axes.minor;
+
+    float logHM = log10(0.5*source->modelPSF->params->data.F32[PM_PAR_I0]);
+
+    // reset source Add/Sub state to recorded
+    if (subtracted) pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+    KapaInitGraph (&graphdata);
+
+    // ** linlog **
+    KapaSelectSection (myKapa, "linlog");
+
+    // examine sources to set data range
+    graphdata.xmin =  -0.05;
+    graphdata.xmax = +30.05;
+    graphdata.ymin = -0.05;
+    graphdata.ymax = +5.05;
+    KapaSetLimits (myKapa, &graphdata);
+
+    KapaSetFont (myKapa, "helvetica", 14);
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "radius (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "log flux (counts)", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, ng, &graphdata);
+    KapaPlotVector (myKapa, ng, rg->data.F32, "x");
+    KapaPlotVector (myKapa, ng, fg->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nb, &graphdata);
+    KapaPlotVector (myKapa, nb, rb->data.F32, "x");
+    KapaPlotVector (myKapa, nb, fb->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 0;
+    graphdata.size = 0.0;
+    graphdata.style = 0;
+    KapaPrepPlot   (myKapa, rmod->n, &graphdata);
+    KapaPlotVector (myKapa, rmod->n, rmod->data.F32, "x");
+    KapaPlotVector (myKapa, rmod->n, fmin->data.F32, "y");
+    plotline (myKapa, &graphdata, 0.0, logHM, 30.0, logHM);
+    plotline (myKapa, &graphdata, 0.5*FWHM_MINOR, 0.0, 0.5*FWHM_MINOR, 5.0);
+    graphdata.ltype = 1;
+    plotline (myKapa, &graphdata, 0.5*MOMENTS_MINOR, 0.0, 0.5*MOMENTS_MINOR, 5.0);
+    graphdata.ltype = 0;
+	
+    graphdata.color = KapaColorByName ("green");
+    graphdata.ptype = 0;
+    graphdata.size = 0.0;
+    graphdata.style = 0;
+    KapaPrepPlot   (myKapa, rmod->n, &graphdata);
+    KapaPlotVector (myKapa, rmod->n, rmod->data.F32, "x");
+    KapaPlotVector (myKapa, rmod->n, fmaj->data.F32, "y");
+    plotline (myKapa, &graphdata, 0.5*FWHM_MAJOR, 0.0, 0.5*FWHM_MAJOR, 5.0);
+    graphdata.ltype = 1;
+    plotline (myKapa, &graphdata, 0.5*MOMENTS_MAJOR, 0.0, 0.5*MOMENTS_MAJOR, 5.0);
+    graphdata.ltype = 0;
+	
+    for (int i = 0; i < rmod->n; i++) {
+	rmod->data.F32[i] = log10(rmod->data.F32[i]);
+    }
+
+    // ** loglog **
+    KapaSelectSection (myKapa, "loglog");
+
+    // examine sources to set data range
+    graphdata.xmin = -1.51;
+    graphdata.xmax = +1.51;
+    graphdata.ymin = -0.05;
+    graphdata.ymax = +5.05;
+    graphdata.color = KapaColorByName ("black");
+    KapaSetLimits (myKapa, &graphdata);
+
+    KapaSetFont (myKapa, "helvetica", 14);
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "log radius (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "log flux (counts)", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, ng, &graphdata);
+    KapaPlotVector (myKapa, ng, Rg->data.F32, "x");
+    KapaPlotVector (myKapa, ng, fg->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, nb, &graphdata);
+    KapaPlotVector (myKapa, nb, Rb->data.F32, "x");
+    KapaPlotVector (myKapa, nb, fb->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 0;
+    graphdata.size = 0.0;
+    graphdata.style = 0;
+    KapaPrepPlot   (myKapa, rmod->n, &graphdata);
+    KapaPlotVector (myKapa, rmod->n, rmod->data.F32, "x");
+    KapaPlotVector (myKapa, rmod->n, fmin->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("green");
+    graphdata.ptype = 0;
+    graphdata.size = 0.0;
+    graphdata.style = 0;
+    KapaPrepPlot   (myKapa, rmod->n, &graphdata);
+    KapaPlotVector (myKapa, rmod->n, rmod->data.F32, "x");
+    KapaPlotVector (myKapa, rmod->n, fmaj->data.F32, "y");
+
+    psFree (rmod);
+    psFree (fmin);
+    psFree (fmaj);
+
+    psFree (rg);
+    psFree (Rg);
+    psFree (fg);
+    psFree (rb);
+    psFree (Rb);
+    psFree (fb);
+    return true;
+}
+
+bool psphotVisualPlotRadialProfiles (psMetadata *recipe, psArray *sources) {
+
+    KapaSection section;  // put the positive profile in one and the residuals in another?
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (2);
+    if (myKapa == -1) return false;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    bool status;
+    psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    KapaClearPlots (myKapa);
+    // first section : mag vs CR nSigma
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.x = 0.0;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "linlog");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    // first section : mag vs CR nSigma
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.x = 0.0;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "loglog");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    // loop over the PSF stars
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        psphotVisualPlotRadialProfile (myKapa, source, maskVal);
+
+        // pause and wait for user input:
+        // continue, save (provide name), ??
+        char key[10];
+        fprintf (stdout, "[e]rase and continue? [o]verplot and continue? [s]kip rest of stars? : ");
+        if (!fgets(key, 8, stdin)) {
+            psWarning("Unable to read option");
+        }
+        if (key[0] == 'e') {
+            KapaClearPlots (myKapa);
+        }
+        if (key[0] == 's') {
+            break;
+        }
+    }
+
+    return true;
+}
+
+bool psphotVisualShowFlags (psArray *sources) {
+
+    int NoverlayE, NOVERLAYE;
+    int NoverlayO, NOVERLAYO;
+    KiiOverlay *overlayE, *overlayO;
+
+    psEllipseMoments emoments;
+    psEllipseAxes axes;
+
+    // XXX skip this for now: it is not very clear
+    return true;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (1);
+    if (myKapa == -1) return false;
+
+    // note: this uses the Ohana allocation tools:
+    NoverlayE = 0;
+    NOVERLAYE = 100;
+    ALLOCATE (overlayE, KiiOverlay, NOVERLAYE);
+
+    NoverlayO = 0;
+    NOVERLAYO = 100;
+    ALLOCATE (overlayO, KiiOverlay, NOVERLAYO);
+
+    for (int i = 0; i < sources->n; i++) {
+
+        float Xo, Yo, Rmaj, Rmin, cs, sn;
+
+        pmSource *source = sources->data[i];
+        if (source == NULL) continue;
+
+        pmMoments *moments = source->moments;
+        if (0) {
+            emoments.x2 = moments->Mxx;
+            emoments.y2 = moments->Myy;
+            emoments.xy = moments->Mxy;
+            Xo = moments->Mx;
+            Yo = moments->My;
+
+            axes = psEllipseMomentsToAxes (emoments, 20.0);
+            Rmaj = 2.0*axes.major;
+            Rmin = 2.0*axes.minor;
+            cs = cos(axes.theta);
+            sn = sin(axes.theta);
+        } else {
+            Rmaj = Rmin = 5.0;
+            cs = 1.0;
+            sn = 0.0;
+            Xo = source->peak->xf;
+            Yo = source->peak->yf;
+        }
+
+        unsigned short int flagMask = 0x01;
+        for (int j = 0; j < 8; j++) {
+            if (source->mode & flagMask) {
+                overlayE[NoverlayE].type = KII_OVERLAY_LINE;
+                overlayE[NoverlayE].x = Xo;
+                overlayE[NoverlayE].y = Yo;
+
+                float phi = j*M_PI/4.0;
+                overlayE[NoverlayE].dx = +Rmaj*cos(phi)*cs - Rmin*sin(phi)*sn;
+                overlayE[NoverlayE].dy = +Rmaj*cos(phi)*sn + Rmin*sin(phi)*cs;
+                overlayE[NoverlayE].angle = 0;
+                overlayE[NoverlayE].text = NULL;
+                NoverlayE ++;
+                CHECK_REALLOCATE (overlayE, KiiOverlay, NOVERLAYE, NoverlayE, 100);
+            }
+            flagMask <<= 1;
+
+            if (source->mode & flagMask) {
+                overlayO[NoverlayO].type = KII_OVERLAY_LINE;
+                overlayO[NoverlayO].x = Xo + 1;
+                overlayO[NoverlayO].y = Yo;
+
+                float phi = j*M_PI/4.0;
+                overlayO[NoverlayO].dx = +Rmaj*cos(phi)*cs - Rmin*sin(phi)*sn;
+                overlayO[NoverlayO].dy = +Rmaj*cos(phi)*sn + Rmin*sin(phi)*cs;
+                overlayO[NoverlayO].angle = 0;
+                overlayO[NoverlayO].text = NULL;
+                NoverlayO ++;
+                CHECK_REALLOCATE (overlayO, KiiOverlay, NOVERLAYO, NoverlayO, 100);
+            }
+            flagMask <<= 1;
+        }
+    }
+
+    KiiLoadOverlay (myKapa, overlayE, NoverlayE, "red");
+    KiiLoadOverlay (myKapa, overlayO, NoverlayO, "yellow");
+    FREE (overlayE);
+    FREE (overlayO);
+
+    fprintf (stdout, "even bits (0x0001, 0x0004, ... : red\n");
+    fprintf (stdout, "odd bits (0x0002, 0x0008, ... : yellow\n");
+    pmVisualAskUser(NULL);
+
+    return true;
+}
+
+bool psphotVisualShowSourceSize_Single (int myKapa, psArray *sources, pmSourceMode mode, bool keep, float scale, char *color) {
+
+    int Noverlay;
+    KiiOverlay *overlay;
+
+    psEllipseMoments emoments;
+    psEllipseAxes axes;
+
+    // note: this uses the Ohana allocation tools:
+    ALLOCATE (overlay, KiiOverlay, sources->n);
+
+    Noverlay = 0;
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+        if (source == NULL) continue;
+
+        if (mode) {
+            if (keep) {
+                if (!(source->mode & mode)) continue;
+            } else {
+                if (source->mode & mode) continue;
+            }
+        }
+
+        pmMoments *moments = source->moments;
+        if (moments == NULL) continue;
+
+        overlay[Noverlay].type = KII_OVERLAY_CIRCLE;
+        overlay[Noverlay].x = moments->Mx;
+        overlay[Noverlay].y = moments->My;
+
+        emoments.x2 = moments->Mxx;
+        emoments.y2 = moments->Myy;
+        emoments.xy = moments->Mxy;
+
+        axes = psEllipseMomentsToAxes (emoments, 20.0);
+
+        overlay[Noverlay].dx = scale*2.0*axes.major;
+        overlay[Noverlay].dy = scale*2.0*axes.minor;
+        overlay[Noverlay].angle = axes.theta * PS_DEG_RAD;
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+    }
+
+    KiiLoadOverlay (myKapa, overlay, Noverlay, color);
+    FREE (overlay);
+
+    return true;
+}
+
+bool psphotVisualShowSourceSize (pmReadout *readout, psArray *sources) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (1);
+    if (myKapa == -1) return false;
+
+    KiiEraseOverlay (myKapa, "red");
+    KiiEraseOverlay (myKapa, "blue");
+    KiiEraseOverlay (myKapa, "green");
+    KiiEraseOverlay (myKapa, "yellow");
+
+    psphotVisualShowSourceSize_Single (myKapa, sources, PM_SOURCE_MODE_EXT_LIMIT | PM_SOURCE_MODE_DEFECT | PM_SOURCE_MODE_CR_LIMIT | PM_SOURCE_MODE_SATSTAR, 0, 1.0, "green");
+    psphotVisualShowSourceSize_Single (myKapa, sources, PM_SOURCE_MODE_EXT_LIMIT, 1, 1.0, "blue");
+    psphotVisualShowSourceSize_Single (myKapa, sources, PM_SOURCE_MODE_CR_LIMIT, 1, 1.0, "red");
+    psphotVisualShowSourceSize_Single (myKapa, sources, PM_SOURCE_MODE_DEFECT, 1, 2.0, "red");
+    psphotVisualShowSourceSize_Single (myKapa, sources, PM_SOURCE_MODE_SATSTAR, 1, 1.0, "yellow");
+
+    fprintf (stdout, "red: CR; blue: EXTENDED; green: PSF-like; yellow: SATSTAR\n");
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualPlotSourceSize (psMetadata *recipe, psMetadata *analysis, psArray *sources) {
+
+    bool status;
+    Graphdata graphdata;
+    KapaSection section;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (2);
+    if (myKapa == -1) return false;
+
+    KapaClearPlots (myKapa);
+    KapaInitGraph (&graphdata);
+    KapaSetFont (myKapa, "courier", 14);
+
+    // select the max psfX,Y values for the plot limits
+    float Xmin = 1000.0, Xmax = 0.0;
+    float Ymin = 1000.0, Ymax = 0.0;
+    {
+        int nRegions = psMetadataLookupS32 (&status, analysis, "PSF.CLUMP.NREGIONS");
+        for (int n = 0; n < nRegions; n++) {
+
+            char regionName[64];
+            snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", n);
+            psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+
+            float psfX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");
+            float psfY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");
+            float psfdX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");
+            float psfdY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");
+
+            float X0 = psfX - 10.0*psfdX;
+            float X1 = psfX + 10.0*psfdX;
+            float Y0 = psfY - 10.0*psfdY;
+            float Y1 = psfY + 10.0*psfdY;
+
+            if (isfinite(X0)) { Xmin = PS_MIN(Xmin, X0); }
+            if (isfinite(X1)) { Xmax = PS_MAX(Xmax, X1); }
+            if (isfinite(Y0)) { Ymin = PS_MIN(Ymin, Y0); }
+            if (isfinite(Y1)) { Ymax = PS_MAX(Ymax, Y1); }
+        }
+    }
+    Xmin = PS_MAX(Xmin, -0.1);
+    Ymin = PS_MAX(Ymin, -0.1);
+
+    // storage vectors for data to be plotted
+    psVector *xSAT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *ySAT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mSAT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *sSAT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    psVector *xPSF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yPSF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mPSF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *sPSF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    psVector *xEXT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yEXT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mEXT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *sEXT = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    psVector *xDEF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yDEF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mDEF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *sDEF = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    psVector *xLOW = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yLOW = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mLOW = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *sLOW = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    psVector *xCR = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yCR = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *mCR = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *sCR = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // construct the vectors
+    int nSAT = 0;
+    int nEXT = 0;
+    int nPSF = 0;
+    int nDEF = 0;
+    int nLOW = 0;
+    int nCR  = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->moments == NULL) continue;
+
+	// only plot the measured sources...
+        if (!(source->tmpFlags & PM_SOURCE_TMPF_SIZE_MEASURED)) continue;
+
+        if (source->mode & PM_SOURCE_MODE_CR_LIMIT) {
+            xCR->data.F32[nCR] = source->moments->Mxx;
+            yCR->data.F32[nCR] = source->moments->Myy;
+            mCR->data.F32[nCR] = -2.5*log10(source->moments->Sum);
+            sCR->data.F32[nCR] = source->extNsigma;
+            nCR++;
+        }
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+            xSAT->data.F32[nSAT] = source->moments->Mxx;
+            ySAT->data.F32[nSAT] = source->moments->Myy;
+            mSAT->data.F32[nSAT] = -2.5*log10(source->moments->Sum);
+            sSAT->data.F32[nSAT] = source->extNsigma;
+            nSAT++;
+        }
+        if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+            xEXT->data.F32[nEXT] = source->moments->Mxx;
+            yEXT->data.F32[nEXT] = source->moments->Myy;
+            mEXT->data.F32[nEXT] = -2.5*log10(source->moments->Sum);
+            sEXT->data.F32[nEXT] = source->extNsigma;
+            nEXT++;
+            continue;
+        }
+        if (source->mode & PM_SOURCE_MODE_DEFECT) {
+            xDEF->data.F32[nDEF] = source->moments->Mxx;
+            yDEF->data.F32[nDEF] = source->moments->Myy;
+            mDEF->data.F32[nDEF] = -2.5*log10(source->moments->Sum);
+            sDEF->data.F32[nDEF] = source->extNsigma;
+            nDEF++;
+            continue;
+        }
+        if (source->errMag > 0.1) {
+            xLOW->data.F32[nLOW] = source->moments->Mxx;
+            yLOW->data.F32[nLOW] = source->moments->Myy;
+            mLOW->data.F32[nLOW] = -2.5*log10(source->moments->Sum);
+            sLOW->data.F32[nLOW] = source->extNsigma;
+            nLOW++;
+            continue;
+        }
+        xPSF->data.F32[nPSF] = source->moments->Mxx;
+        yPSF->data.F32[nPSF] = source->moments->Myy;
+        mPSF->data.F32[nPSF] = -2.5*log10(source->moments->Sum);
+        sPSF->data.F32[nPSF] = source->extNsigma;
+        nPSF++;
+    }
+
+    xSAT->n = nSAT;
+    ySAT->n = nSAT;
+    mSAT->n = nSAT;
+    sSAT->n = nSAT;
+
+    xPSF->n = nPSF;
+    yPSF->n = nPSF;
+    mPSF->n = nPSF;
+    sPSF->n = nPSF;
+
+    xEXT->n = nEXT;
+    yEXT->n = nEXT;
+    mEXT->n = nEXT;
+    sEXT->n = nEXT;
+
+    xCR->n = nCR;
+    yCR->n = nCR;
+    mCR->n = nCR;
+    sCR->n = nCR;
+
+    xDEF->n = nDEF;
+    yDEF->n = nDEF;
+    mDEF->n = nDEF;
+    sDEF->n = nDEF;
+
+    xLOW->n = nLOW;
+    yLOW->n = nLOW;
+    mLOW->n = nLOW;
+    sLOW->n = nLOW;
+
+    // four sections: MxxMyy, MagMxx, MagMyy, MagSigma
+
+    // first section: MxxMyy
+    section.dx = 0.75;
+    section.dy = 0.60;
+    section.x  = 0.00;
+    section.y  = 0.00;
+    section.name = psStringCopy ("MxxMyy");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = Xmin;
+    graphdata.ymin = Ymin;
+    graphdata.xmax = Xmax;
+    graphdata.ymax = Ymax;
+    KapaSetLimits (myKapa, &graphdata);
+
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "M_xx| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "M_yy| (pixels)", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nPSF, &graphdata);
+    KapaPlotVector (myKapa, nPSF, xPSF->data.F32, "x");
+    KapaPlotVector (myKapa, nPSF, yPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nEXT, &graphdata);
+    KapaPlotVector (myKapa, nEXT, xEXT->data.F32, "x");
+    KapaPlotVector (myKapa, nEXT, yEXT->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nDEF, &graphdata);
+    KapaPlotVector (myKapa, nDEF, xDEF->data.F32, "x");
+    KapaPlotVector (myKapa, nDEF, yDEF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nCR, &graphdata);
+    KapaPlotVector (myKapa, nCR, xCR->data.F32, "x");
+    KapaPlotVector (myKapa, nCR, yCR->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nSAT, &graphdata);
+    KapaPlotVector (myKapa, nSAT, xSAT->data.F32, "x");
+    KapaPlotVector (myKapa, nSAT, ySAT->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nLOW, &graphdata);
+    KapaPlotVector (myKapa, nLOW, xLOW->data.F32, "x");
+    KapaPlotVector (myKapa, nLOW, yLOW->data.F32, "y");
+
+    // second section: MagMyy
+    section.dx = 0.75;
+    section.dy = 0.20;
+    section.x  = 0.00;
+    section.y  = 0.80;
+    section.name = psStringCopy ("MagMyy");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = -17.1;
+    graphdata.xmax =  -6.9;
+    graphdata.ymin = Ymin;
+    graphdata.ymax = Ymax;
+    KapaSetLimits (myKapa, &graphdata);
+
+    strcpy (graphdata.labels, "0210");
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "inst mag", KAPA_LABEL_XP);
+    KapaSendLabel (myKapa, "M_yy| (pixels)", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nPSF, &graphdata);
+    KapaPlotVector (myKapa, nPSF, mPSF->data.F32, "x");
+    KapaPlotVector (myKapa, nPSF, yPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nEXT, &graphdata);
+    KapaPlotVector (myKapa, nEXT, mEXT->data.F32, "x");
+    KapaPlotVector (myKapa, nEXT, yEXT->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nDEF, &graphdata);
+    KapaPlotVector (myKapa, nDEF, mDEF->data.F32, "x");
+    KapaPlotVector (myKapa, nDEF, yDEF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nCR, &graphdata);
+    KapaPlotVector (myKapa, nCR, mCR->data.F32, "x");
+    KapaPlotVector (myKapa, nCR, yCR->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nSAT, &graphdata);
+    KapaPlotVector (myKapa, nSAT, mSAT->data.F32, "x");
+    KapaPlotVector (myKapa, nSAT, ySAT->data.F32, "y");
+
+    // third section: MagMxx
+    section.dx = 0.25;
+    section.dy = 0.60;
+    section.x  = 0.80;
+    section.y  = 0.00;
+    section.name = psStringCopy ("MagMxx");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmin = Xmin;
+    graphdata.xmax = Xmax;
+    graphdata.ymin =  -6.9;
+    graphdata.ymax = -17.1;
+    KapaSetLimits (myKapa, &graphdata);
+
+    strcpy (graphdata.labels, "2001");
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "M_xx| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "inst mag", KAPA_LABEL_YP);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nPSF, &graphdata);
+    KapaPlotVector (myKapa, nPSF, xPSF->data.F32, "x");
+    KapaPlotVector (myKapa, nPSF, mPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nEXT, &graphdata);
+    KapaPlotVector (myKapa, nEXT, xEXT->data.F32, "x");
+    KapaPlotVector (myKapa, nEXT, mEXT->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nDEF, &graphdata);
+    KapaPlotVector (myKapa, nDEF, xDEF->data.F32, "x");
+    KapaPlotVector (myKapa, nDEF, mDEF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nCR, &graphdata);
+    KapaPlotVector (myKapa, nCR, xCR->data.F32, "x");
+    KapaPlotVector (myKapa, nCR, mCR->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nSAT, &graphdata);
+    KapaPlotVector (myKapa, nSAT, xSAT->data.F32, "x");
+    KapaPlotVector (myKapa, nSAT, mSAT->data.F32, "y");
+
+    // fourth section: MagSigma
+    section.dx = 0.75;
+    section.dy = 0.15;
+    section.x  = 0.00;
+    section.y  = 0.65;
+    section.name = psStringCopy ("MagSigma");
+    KapaSetSection (myKapa, &section);
+    psFree (section.name);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.xmax =  -6.9;
+    graphdata.xmin = -17.1;
+    graphdata.ymin = -20.1;
+    graphdata.ymax = +20.1;
+    KapaSetLimits (myKapa, &graphdata);
+
+    strcpy (graphdata.labels, "0100");
+    KapaBox (myKapa, &graphdata);
+    // KapaSendLabel (myKapa, "inst mag", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "EXT&ss&c", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nPSF, &graphdata);
+    KapaPlotVector (myKapa, nPSF, mPSF->data.F32, "x");
+    KapaPlotVector (myKapa, nPSF, sPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nEXT, &graphdata);
+    KapaPlotVector (myKapa, nEXT, mEXT->data.F32, "x");
+    KapaPlotVector (myKapa, nEXT, sEXT->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nDEF, &graphdata);
+    KapaPlotVector (myKapa, nDEF, mDEF->data.F32, "x");
+    KapaPlotVector (myKapa, nDEF, sDEF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nCR, &graphdata);
+    KapaPlotVector (myKapa, nCR, mCR->data.F32, "x");
+    KapaPlotVector (myKapa, nCR, sCR->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 7;
+    graphdata.size = 1.0;
+    graphdata.style = 2;
+    KapaPrepPlot   (myKapa, nSAT, &graphdata);
+    KapaPlotVector (myKapa, nSAT, mSAT->data.F32, "x");
+    KapaPlotVector (myKapa, nSAT, sSAT->data.F32, "y");
+
+    // draw N circles to outline the clumps
+    {
+        KapaSelectSection (myKapa, "MxxMyy");
+
+        // draw a circle centered on psfX,Y with size of the psf limit
+        psVector *xLimit  = psVectorAlloc (120, PS_TYPE_F32);
+        psVector *yLimit  = psVectorAlloc (120, PS_TYPE_F32);
+
+        int nRegions = psMetadataLookupS32 (&status, analysis, "PSF.CLUMP.NREGIONS");
+        float PSF_CLUMP_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_CLUMP_NSIGMA");
+
+        graphdata.color = KapaColorByName ("blue");
+        graphdata.style = 0;
+
+        graphdata.xmin = Xmin;
+        graphdata.ymin = Ymin;
+        graphdata.xmax = Xmax;
+        graphdata.ymax = Ymax;
+        KapaSetLimits (myKapa, &graphdata);
+
+        for (int n = 0; n < nRegions; n++) {
+
+            char regionName[64];
+            snprintf (regionName, 64, "PSF.CLUMP.REGION.%03d", n);
+            psMetadata *regionMD = psMetadataLookupPtr (&status, analysis, regionName);
+
+            float psfX  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.X");
+            float psfY  = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.Y");
+            float psfdX = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DX");
+            float psfdY = psMetadataLookupF32 (&status, regionMD, "PSF.CLUMP.DY");
+            float Rx = psfdX * PSF_CLUMP_NSIGMA;
+            float Ry = psfdY * PSF_CLUMP_NSIGMA;
+
+            for (int i = 0; i < xLimit->n; i++) {
+                xLimit->data.F32[i] = Rx*cos(i*2.0*M_PI/120.0) + psfX;
+                yLimit->data.F32[i] = Ry*sin(i*2.0*M_PI/120.0) + psfY;
+            }
+            KapaPrepPlot (myKapa, xLimit->n, &graphdata);
+            KapaPlotVector (myKapa, xLimit->n, xLimit->data.F32, "x");
+            KapaPlotVector (myKapa, yLimit->n, yLimit->data.F32, "y");
+        }
+        psFree (xLimit);
+        psFree (yLimit);
+    }
+
+    psFree (xSAT);
+    psFree (ySAT);
+    psFree (mSAT);
+    psFree (sSAT);
+
+    psFree (xEXT);
+    psFree (yEXT);
+    psFree (mEXT);
+    psFree (sEXT);
+
+    psFree (xPSF);
+    psFree (yPSF);
+    psFree (mPSF);
+    psFree (sPSF);
+
+    psFree (xDEF);
+    psFree (yDEF);
+    psFree (mDEF);
+    psFree (sDEF);
+
+    psFree (xCR);
+    psFree (yCR);
+    psFree (mCR);
+    psFree (sCR);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowResidualImage (pmReadout *readout) {
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (1);
+    if (myKapa == -1) return false;
+
+    psphotVisualScaleImage (myKapa, readout->image, readout->mask, "resid", 1);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualPlotApResid (psArray *sources, float mean, float error) {
+
+    Graphdata graphdata;
+    float lineX[2], lineY[2];
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (2);
+    if (myKapa == -1) return false;
+
+    KapaClearPlots (myKapa);
+    KapaInitGraph (&graphdata);
+
+    psVector *x = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *y = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *dy = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    graphdata.xmin = +32.0;
+    graphdata.xmax = -32.0;
+    graphdata.ymin = +32.0;
+    graphdata.ymax = -32.0;
+
+    // construct the plot vectors
+    int n = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!source) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+        if (!isfinite (source->apMag)) continue;
+        if (!isfinite (source->psfMag)) continue;
+
+        x->data.F32[n] = source->psfMag;
+        y->data.F32[n] = source->apMag - source->psfMag;
+        dy->data.F32[n] = source->errMag;
+        graphdata.xmin = PS_MIN(graphdata.xmin, x->data.F32[n]);
+        graphdata.xmax = PS_MAX(graphdata.xmax, x->data.F32[n]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, y->data.F32[n]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, y->data.F32[n]);
+
+        n++;
+    }
+    x->n = y->n = dy->n = n;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX test
+    graphdata.xmin = -17.0;
+    graphdata.xmax =  -9.0;
+    graphdata.ymin = -0.31;
+    graphdata.ymax = +0.31;
+
+    KapaSetLimits (myKapa, &graphdata);
+
+    KapaSetFont (myKapa, "helvetica", 14);
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "PSF Mag", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "Ap Mag - PSF Mag", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    graphdata.etype |= 0x01;
+    KapaPrepPlot (myKapa, n, &graphdata);
+    KapaPlotVector (myKapa, n, x->data.F32, "x");
+    KapaPlotVector (myKapa, n, y->data.F32, "y");
+    KapaPlotVector (myKapa, n, dy->data.F32, "dym");
+    KapaPlotVector (myKapa, n, dy->data.F32, "dyp");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 0;
+    graphdata.etype = 0;
+    lineX[0] = graphdata.xmin;
+    lineX[1] = graphdata.xmax;
+    lineY[0] = lineY[1] = mean;
+    KapaPrepPlot (myKapa, 2, &graphdata);
+    KapaPlotVector (myKapa, 2, lineX, "x");
+    KapaPlotVector (myKapa, 2, lineY, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 0;
+    graphdata.etype = 0;
+    lineX[0] = graphdata.xmin;
+    lineX[1] = graphdata.xmax;
+    lineY[0] = lineY[1] = mean + error;
+    KapaPrepPlot (myKapa, 2, &graphdata);
+    KapaPlotVector (myKapa, 2, lineX, "x");
+    KapaPlotVector (myKapa, 2, lineY, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 0;
+    graphdata.etype = 0;
+    lineX[0] = graphdata.xmin;
+    lineX[1] = graphdata.xmax;
+    lineY[0] = lineY[1] = mean - error;
+    KapaPrepPlot (myKapa, 2, &graphdata);
+    KapaPlotVector (myKapa, 2, lineX, "x");
+    KapaPlotVector (myKapa, 2, lineY, "y");
+
+    psFree (x);
+    psFree (y);
+    psFree (dy);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualPlotChisq (psArray *sources) {
+
+    Graphdata graphdata;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int myKapa = psphotKapaChannel (2);
+    if (myKapa == -1) return false;
+
+    KapaClearPlots (myKapa);
+    KapaInitGraph (&graphdata);
+
+    psVector *x = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *y = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    graphdata.xmin = +32.0;
+    graphdata.xmax = -32.0;
+    graphdata.ymin = +32.0;
+    graphdata.ymax = -32.0;
+
+    FILE *f = fopen ("chisq.dat", "w");
+
+    // construct the plot vectors
+    int n = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!source) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+        if (!source->moments) continue;
+        if (!isfinite(source->moments->Sum)) continue;
+        if (!source->modelPSF) continue;
+        if (!isfinite(source->modelPSF->chisq)) continue;
+
+        x->data.F32[n] = -2.5*log10(source->moments->Sum);
+        y->data.F32[n] = source->modelPSF->chisq / source->modelPSF->nDOF;
+        graphdata.xmin = PS_MIN(graphdata.xmin, x->data.F32[n]);
+        graphdata.xmax = PS_MAX(graphdata.xmax, x->data.F32[n]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, y->data.F32[n]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, y->data.F32[n]);
+
+        fprintf (f, "%d %d %f %f\n", i, n, x->data.F32[n], y->data.F32[n]);
+
+        n++;
+    }
+    x->n = y->n = n;
+    fclose (f);
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX test
+    graphdata.xmin = -17.0;
+    graphdata.xmax =  -3.0;
+    graphdata.ymin =  -0.1;
+    graphdata.ymax = +10.1;
+
+    KapaSetLimits (myKapa, &graphdata);
+
+    KapaSetFont (myKapa, "helvetica", 14);
+    KapaBox (myKapa, &graphdata);
+    KapaSendLabel (myKapa, "PSF Mag", KAPA_LABEL_XM);
+    KapaSendLabel (myKapa, "ChiSq", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (myKapa, n, &graphdata);
+    KapaPlotVector (myKapa, n, x->data.F32, "x");
+    KapaPlotVector (myKapa, n, y->data.F32, "y");
+
+    psFree (x);
+    psFree (y);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+bool psphotVisualShowPetrosians (psArray *sources) {
+
+    int Noverlay, NOVERLAY;
+    KiiOverlay *overlay;
+
+    if (!pmVisualIsVisual()) return true;
+
+    int kapa = psphotKapaChannel (1);
+    if (kapa == -1) return false;
+
+    Noverlay = 0;
+    NOVERLAY = 100;
+    ALLOCATE (overlay, KiiOverlay, NOVERLAY);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        if (!source) continue;
+        if (!source->extpars) continue;
+        if (!source->extpars->profile) continue;
+        if (!source->extpars->petrosian_80) continue;
+
+        pmSourceRadialProfile *profile = source->extpars->profile;
+        pmSourceExtendedFlux *petrosian = source->extpars->petrosian_80;
+
+        overlay[Noverlay].type = KII_OVERLAY_CIRCLE;
+        overlay[Noverlay].x = source->peak->xf;
+        overlay[Noverlay].y = source->peak->yf;
+        overlay[Noverlay].dx = 2.0*petrosian->radius;
+        overlay[Noverlay].dy = 2.0*petrosian->radius*profile->axes.minor/profile->axes.major;
+        overlay[Noverlay].angle = profile->axes.theta * PS_DEG_RAD;
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+        CHECK_REALLOCATE (overlay, KiiOverlay, NOVERLAY, Noverlay, 100);
+
+        overlay[Noverlay].type = KII_OVERLAY_CIRCLE;
+        overlay[Noverlay].x = source->peak->xf;
+        overlay[Noverlay].y = source->peak->yf;
+        overlay[Noverlay].dx = 4.0*petrosian->radius;
+        overlay[Noverlay].dy = 4.0*petrosian->radius*profile->axes.minor/profile->axes.major;
+        overlay[Noverlay].angle = profile->axes.theta * PS_DEG_RAD;
+        overlay[Noverlay].text = NULL;
+        Noverlay ++;
+        CHECK_REALLOCATE (overlay, KiiOverlay, NOVERLAY, Noverlay, 100);
+    }
+
+    KiiLoadOverlay (kapa, overlay, Noverlay, "red");
+    FREE (overlay);
+
+    pmVisualAskUser(NULL);
+    return true;
+}
+
+# else
+
+bool psphotVisualShowImage (pmConfig *config, pmReadout *readout) { return true; }
+bool psphotVisualShowBackground (pmConfig *config, const pmFPAview *view, pmReadout *readout) { return true; }
+bool psphotVisualShowSignificance (psImage *image) { return true; }
+bool psphotVisualShowPeaks (pmConfig *config, const pmFPAview *view, pmDetections *detections) { return true; }
+bool psphotVisualShowFootprints (pmConfig *config, const pmFPAview *view, pmDetections *detections) { return true; }
+bool psphotVisualShowMoments (pmConfig *config, const pmFPAview *view, psArray *sources) { return true; }
+bool psphotVisualPlotMoments (pmConfig *config, const pmFPAview *view, psArray *sources) { return true; }
+bool psphotVisualShowRoughClass (pmConfig *config, const pmFPAview *view, psArray *sources) { return true; }
+bool psphotVisualShowPSFStars (pmConfig *config, const pmFPAview *view, pmPSF *psf, psArray *sources) { return true; }
+bool psphotVisualShowSatStars (pmConfig *config, const pmFPAview *view, pmPSF *psf, psArray *sources) { return true; }
+bool psphotVisualShowPSFModel (pmConfig *config, pmReadout *readout, pmPSF *psf) { return true; }
+bool psphotVisualShowFlags (pmConfig *config, const pmFPAview *view, psArray *sources) { return true; }
+bool psphotVisualSourceSize (pmConfig *config, const pmFPAview *view, psArray *sources) { return true; }
+bool psphotVisualShowResidualImage (pmConfig *config, pmReadout *readout) { return true; }
+bool psphotVisualPlotApResid (pmConfig *config, const pmFPAview *view, psArray *sources) { return true; }
+
+# endif
+
+# if (0)
+// *** make a histogram of the source counts in the x and y directions
+psHistogram *nX = psHistogramAlloc (graphdata.xmin, graphdata.xmax, 50.0);
+psHistogram *nY = psHistogramAlloc (graphdata.ymin, graphdata.ymax, 50.0);
+psVectorHistogram (nX, xFaint, NULL, NULL, 0);
+psVectorHistogram (nY, yFaint, NULL, NULL, 0);
+psVector *dX = psVectorAlloc (nX->nums->n, PS_TYPE_F32);
+psVector *vX = psVectorAlloc (nX->nums->n, PS_TYPE_F32);
+psVector *dY = psVectorAlloc (nY->nums->n, PS_TYPE_F32);
+psVector *vY = psVectorAlloc (nY->nums->n, PS_TYPE_F32);
+for (int i = 0; i < nX->nums->n; i++) {
+    dX->data.F32[i] = nX->nums->data.S32[i];
+    vX->data.F32[i] = 0.5*(nX->bounds->data.F32[i] + nX->bounds->data.F32[i+1]);
+}
+for (int i = 0; i < nY->nums->n; i++) {
+    dY->data.F32[i] = nY->nums->data.S32[i];
+    vY->data.F32[i] = 0.5*(nY->bounds->data.F32[i] + nY->bounds->data.F32[i+1]);
+}
+
+graphdata.color = KapaColorByName ("black");
+graphdata.ptype = 0;
+graphdata.size = 0.0;
+graphdata.style = 0;
+KapaPrepPlot (myKapa, dX->n, &graphdata);
+KapaPlotVector (myKapa, dX->n, dX->data.F32, "x");
+KapaPlotVector (myKapa, vX->n, vX->data.F32, "y");
+
+psFree (nX);
+psFree (dX);
+psFree (vX);
+
+psFree (nY);
+psFree (dY);
+psFree (vY);
+
+# endif
+
